feat: improvements from real world use case. make package consumable#1
Open
iamkoch wants to merge 14 commits into
Open
feat: improvements from real world use case. make package consumable#1iamkoch wants to merge 14 commits into
iamkoch wants to merge 14 commits into
Conversation
- 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.
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.
fd4791c to
f1e40a9
Compare
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.