Skip to content

feat: improvements from real world use case. make package consumable#1

Open
iamkoch wants to merge 14 commits into
mainfrom
feat/policy-binding-improvements
Open

feat: improvements from real world use case. make package consumable#1
iamkoch wants to merge 14 commits into
mainfrom
feat/policy-binding-improvements

Conversation

@iamkoch

@iamkoch iamkoch commented Jun 5, 2026

Copy link
Copy Markdown
Owner

No description provided.

iamkoch added 6 commits June 5, 2026 14:30
- guid: replace github.com/pkg/errors with stdlib fmt.Errorf+%w to avoid
  the missing-module build failure.
- eventstore/inmemory: update repository_test to the post-generics API
  (AggregateRootBase[TID], NewGenericIDRepository, GetById returning
  (T, error)).

Tests now pass with 'go test ./...'.
Drop the underscore prefix from private fields on AggregateRootBase
(_changes/_id/_version/_innerApply -> changes/id/version/innerApply).
These fields are unexported so the rename is invisible to consumers, but
the underscore-prefixed Go names are not idiomatic and slightly
distracting in struct definitions.

Also adds:
- a comprehensive package doc explaining the type-switch + injected-
  handler dispatch pattern (the conqueress-specific solution to event
  application without reflection)
- Version() accessor (was previously only writable via SetVersion)
- MarkChangesAsCommitted() for the repository's post-save lifecycle
  (mirrors IntelAgent.Framework's API)
- doc comments on the public surface

No behaviour change. Tests updated to use the Version() accessor.
The Mediator no longer carries an induceDelay flag and no longer sleeps
on command/event dispatch. The core is intentionally deterministic.

Consumers who want artificial delays in tests (e.g. to exercise
eventual-consistency behaviour in downstream projections) should wrap a
handler with a delay decorator at registration time — don't bake test
behaviour into the framework's core dispatcher.

API change:
  - NewMediator(bool) -> NewMediator()

All test callers updated. Commented-out NewMediator(false) lines in
conqueress-mongo/store2_test.go are left as-is (they're historical
comments, not live code).
Adds the metadata that any non-trivial event-sourced system needs but
that the original conqueress didn't model:

  Event interface gains:
    - CorrelationId()  guid.Guid
    - CausationId()    guid.Guid
    - OccurredAt()     time.Time
    - WithMetadata(correlation, causation, occurredAt guid.Guid, time.Time)

  BaseEvent gains matching fields and accessors. Empty values map to
  guid.Empty / zero time — explicit absence rather than panicking on
  unset.

Introduces:

  IntegrationEvent interface
    - extends Event with EventType() string for cross-language wire
      stability ('com.inshur.policy.created' vs Go's 'PolicyCreated').

  BaseCommand + NewBaseCommand(correlation, causation)
    - embeddable base that carries an ID, correlation, causation and
      createdAt. Optional — Command stays a marker interface so existing
      consumers don't have to migrate.

This is what flows through the framework as commands → events →
downstream commands. The application layer can now stamp metadata onto
events emitted by an aggregate via the command's IDs, and downstream
consumers can carry the trace through.

Existing tests updated (mediator_test.go's TestEvent now embeds
*BaseEvent so it satisfies the wider interface). New tests in
eventing_test.go cover BaseEvent.WithMetadata, default zero values, and
NewBaseCommand.
Adds the typed function-alias surface that mirrors IntelAgent.Framework's
HandleCommand<T> delegate:

  HandleCommand[T any]         — typed command handler
  HandleQuery[Q, R any]        — typed query handler (read-side, no causation)
  HandleEvent[T Event]         — typed event handler
  IntegrationEventPublisher    — outbound port for cross-service publishing

These let an application layer compose handlers via factory functions
that close over their dependencies — useful when you don't want to go
through the reflection-based Mediator. The Mediator remains for cases
where loose in-process routing is wanted.

handlers_test.go covers:
- factory composition of a typed HandleCommand
- error propagation
- IntegrationEventPublisher accepting any IntegrationEvent

README rewritten end-to-end with a quick-start walkthrough covering
aggregate definition, command handler composition, and the
domain-event-to-integration-event translation pattern.
The go.mod declaring 'module github.com/iamkoch/conqueress' was living
in the '/conqueress/' subdirectory. This worked locally but broke
external consumption: when a downstream service ran
'go get github.com/iamkoch/conqueress', Go found no module at the
repo root and could not resolve the package paths.

Move the framework's source files to the repo root so the module's
location matches its declared path. Tests pass after the move because
all internal imports reference the module path, not the directory.

Sibling modules (conqueress-mongo, conqueress-firestore, reflection,
sample_domain, tests) remain in their subdirectories with their own
go.mod files; they were already not externally consumable due to
similar path mismatches, but addressing those is out of scope here.
@iamkoch iamkoch changed the title Feat/policy binding improvements feat: improvements from real world use case. make package consumable Jun 5, 2026
iamkoch added 6 commits June 5, 2026 15:16
CI:
- New .github/workflows/test.yml runs go build, go vet and
  'go test -race -count=1 -coverprofile=...' across Go 1.23 and 1.24 on
  every push and pull request, with a coverage summary printed at the
  end.

Race fix:
- Mediator.Publish now waits for every processor goroutine via
  sync.WaitGroup before returning. Previously fire-and-forget, which
  meant downstream side effects were untestable and any handler error
  was silently swallowed. Test sleeps removed; tests now read from the
  resp channel for command-dispatch synchronisation.

README:
- Rewritten end-to-end in British English with the AI prose tells
  scrubbed (no em dashes, no 'not x but y' constructs, no marketing
  vocabulary). Adds a quick test-runner section and a CI pointer.
Per Go's release policy (a release is supported until two newer major
releases ship), the actively supported versions are now 1.25 and 1.26.
The previous 1.23/1.24 matrix was a release behind.
The previous example used the long-hand 'embed + SetInnerApply' form for
every construction site. Switch to the helper:

  item := domain.New[Item]()

This relies on the aggregate satisfying DefaultAggregate[TID] via two
one-line methods (SetBase, GetHandler), shown in the example. Trade-off
is two lines of method declarations per type for one-line construction
at every call site.
BaseProjection, BaseProjectionHandler[T], LoadProjection[T] and
SaveProjection[T] were undocumented. The example shows the canonical
'load, mutate, save' update pattern that the handler provides and notes
that the delegates are the persistence port (any store works behind
them).
A telemetry-heavy system often has high-volume inbound writes that are
observations (sensor readings, webhooks, packets) rather than decisions.
Routing them through commands and aggregates triggers optimistic-
concurrency contention without protecting any invariant.

doc/patterns/facts-vs-commands.md captures the distinction, the criteria
for when a write qualifies as a fact, the recommended pattern (idempotent
upsert to a fact store on a natural key), what you give up, the
anti-patterns to avoid, and references to public material that backs the
guidance.

README gains a Patterns section linking to it.
@iamkoch iamkoch force-pushed the feat/policy-binding-improvements branch from fd4791c to f1e40a9 Compare June 8, 2026 11:04
iamkoch added 2 commits June 10, 2026 08:57
The legacy IEventStore / IGenericIDEventStore interfaces have no
context.Context and no error return on reads, which forces production
implementations to panic on infrastructure failures. Fine for the
in-memory sample, not for a real database store.

Adds, as a non-breaking sibling:

  Store[TID]                      — ctx-aware persistence port
  ContextRepository[T, TID]       — ctx-aware load/save
  NewContextRepository            — constructor

Semantics tightened relative to the legacy repository:
- Save marks the aggregate's changes committed on success (mirrors the
  C# reference behaviour), so subsequent saves append only new events.
- GetById distinguishes empty stream (ErrAggregateNotFound) from
  infrastructure failure (propagated error).
- The store contract documents version assignment (WithVersion from
  expectedVersion+1) and concurrency failure semantics.

Covered by context_test.go using an in-memory fake store: version
assignment, commit-on-save, rehydration, not-found, error propagation
and concurrency conflict.
The old conqueress-mongo sample was broken three ways: its go.mod never
required the core module (so it did not build), its module path
(github.com/iamkoch/conqueress/mongo) did not match its directory
(conqueress-mongo/, so go get could never resolve it), and it was
design-stale (hardcoded database name, Guid-only legacy IEventStore,
panics on read errors, 2022-era driver).

Replaced wholesale with mongo/ — directory matching the module path so
it is fetchable:

- Store[TID Stringer] implementing the context-aware
  eventstore.Store[TID]. Generic over any aggregate ID that renders to
  a stable string; composite IDs are first-class.
- Transactional append: stream-head check plus event inserts in one
  transaction, losing cleanly with ErrConcurrencyException on conflict.
  Version assignment via WithVersion from expectedVersion+1.
- TypeRegistry with explicit wire names (Register[T](r, name)), so Go
  type renames never break persisted streams. Fails loudly on
  unregistered types in both directions.
- Correlation, causation and OccurredAt persisted on the envelope;
  empty guids stored as absent fields.
- EnsureIndexes for the unique (aggregate_id, version) index.
- Configurable database and collection names.

Tests cover registry round-tripping with full metadata, loud failure on
unregistered types, the empty-guid mapping, and compile-time
conformance of Store[compositeID] against the port. Mongo-integration
coverage lands via the consuming service's journey tests, where a real
replica set exists.

CI gains a step running the mongo module's tests (separate module, so
the root ./... does not reach it). README documents the adapter and
downgrades conqueress-firestore to a legacy reference sample.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant