Skip to content

feat: extract EventStore, Timeline, and DLQ as self-registering modules (#90)#125

Merged
intel352 merged 2 commits intomainfrom
feat/issue-90-domain-services
Feb 23, 2026
Merged

feat: extract EventStore, Timeline, and DLQ as self-registering modules (#90)#125
intel352 merged 2 commits intomainfrom
feat/issue-90-domain-services

Conversation

@intel352
Copy link
Contributor

Summary

Follows the feature flag module pattern (PR #56) to extract three domain services from hardcoded cmd/server/main.go wiring into self-registering module types.

New module types

Module Type Description Config
eventstore.service Wraps SQLiteEventStore with config-driven setup db_path, retention_days
timeline.service Wraps TimelineHandler, ReplayHandler, and BackfillMockDiffHandler event_store (service reference)
dlq.service Wraps DLQHandler with in-memory store max_retries, retention_days

Changes

  • module/: Three new module files (eventstore_service.go, timeline_service.go, dlq_service.go) implementing modular.Module + ProvidesServices()
  • plugins/: Three new plugin packages (eventstore, timeline, dlq) with factory registration
  • cmd/server/main.go: Plugins loaded; initStores() now discovers services from the registry with backward-compatible fallbacks
  • admin/config.yaml: New module declarations for admin-event-store, admin-timeline, admin-dlq
  • schema/: Module schemas and type registrations added
  • Tests: Full coverage for all three modules and plugins

Backward compatibility

The initStores() method first checks the service registry for modules registered by config. If not found, it falls back to the previous direct-construction approach, ensuring existing deployments without the new config entries continue to work.

Closes #90

…es (#90)

Follow the feature flag module pattern (PR #56) to extract three domain
services into self-registering module types:

- eventstore.service: wraps SQLiteEventStore with config-driven setup
- timeline.service: wraps TimelineHandler and ReplayHandler
- dlq.service: wraps DLQHandler

Each module implements ProvidesServices() for auto-discovery and is
declared in admin/config.yaml. Hardcoded wiring removed from main.go
with backward-compatible fallbacks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 23, 2026 10:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extracts EventStore, Timeline/Replay/Backfill, and DLQ wiring from cmd/server/main.go into config-driven, self-registering module types (following the feature-flag module pattern), with schema + admin config updates to enable the services via YAML.

Changes:

  • Added new module types: eventstore.service, timeline.service, dlq.service (+ tests).
  • Added new core plugins to register the above module factories (+ tests) and loaded them in the server.
  • Updated schema registrations and admin/config.yaml to declare the new modules; updated server store/handler initialization to auto-discover services with fallbacks.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
schema/schema.go Registers new module type strings as core module types.
schema/module_schema.go Adds UI schema definitions for eventstore.service, timeline.service, and dlq.service.
plugins/eventstore/plugin.go Registers eventstore.service module factory.
plugins/eventstore/plugin_test.go Tests plugin metadata + factory presence/creation.
plugins/timeline/plugin.go Registers timeline.service via a deferred-resolve module wrapper.
plugins/timeline/plugin_test.go Tests plugin metadata + factory presence/creation.
plugins/dlq/plugin.go Registers dlq.service module factory.
plugins/dlq/plugin_test.go Tests plugin metadata + factory presence/creation.
module/eventstore_service.go Implements EventStoreServiceModule wrapping SQLiteEventStore.
module/eventstore_service_test.go Unit tests for EventStoreServiceModule.
module/timeline_service.go Implements TimelineServiceModule exposing timeline/replay/backfill muxes.
module/timeline_service_test.go Unit tests for TimelineServiceModule.
module/dlq_service.go Implements DLQServiceModule exposing DLQ mux + store.
module/dlq_service_test.go Unit tests for DLQServiceModule.
cmd/server/main.go Loads new plugins; initStores() discovers services from registry with fallbacks.
admin/config.yaml Declares admin-event-store, admin-timeline, and admin-dlq modules.
module/query_handler_test.go Removes trailing whitespace line.
module/command_handler_test.go Removes trailing whitespace line.

Comment on lines +11 to +15
// DLQServiceConfig holds the configuration for the DLQ service module.
type DLQServiceConfig struct {
MaxRetries int `yaml:"max_retries" default:"3"`
RetentionDays int `yaml:"retention_days" default:"30"`
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DLQServiceConfig exposes max_retries and retention_days, but the module currently doesn't apply either setting to the DLQ store or handler (NewDLQHandler doesn't accept options and the in-memory store doesn't enforce retention). This makes the config + schema misleading; either implement the behavior (e.g., enforce defaults when creating DLQEntry / schedule purges) or remove these fields from config/schema until supported.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +17
// EventStoreServiceConfig holds the configuration for the event store service module.
type EventStoreServiceConfig struct {
DBPath string `yaml:"db_path" default:"data/events.db"`
RetentionDays int `yaml:"retention_days" default:"90"`
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventStoreServiceConfig includes retention_days, but the module only stores the value and exposes it via RetentionDays(); it is not used to prune events or configure the underlying store. If retention is not implemented yet, consider removing the config/schema field (or clearly documenting it as unused), or implement a purge mechanism that uses this value.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +49
cfg.MaxRetries = v
}
if v, ok := config["retention_days"].(int); ok {
cfg.RetentionDays = v
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory only reads numeric config values when they are typed as int. Elsewhere in the repo, factories accept both int and float64 because YAML numbers may decode as float64 (see plugins/api/plugin.go:getIntConfig and plugins/http/modules.go). Handle float64 here too so max_retries/retention_days are not silently ignored.

Suggested change
cfg.MaxRetries = v
}
if v, ok := config["retention_days"].(int); ok {
cfg.RetentionDays = v
cfg.MaxRetries = v
} else if v, ok := config["max_retries"].(float64); ok {
cfg.MaxRetries = int(v)
}
if v, ok := config["retention_days"].(int); ok {
cfg.RetentionDays = v
} else if v, ok := config["retention_days"].(float64); ok {
cfg.RetentionDays = int(v)

Copilot uses AI. Check for mistakes.
cfg.DBPath = v
}
if v, ok := config["retention_days"].(int); ok {
cfg.RetentionDays = v
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This factory only reads retention_days when it is typed as int. Other factories in the repo also accept float64 for YAML-decoded numbers (e.g., plugins/api/plugin.go:getIntConfig, plugins/http/modules.go). Handle float64 here as well to avoid silently ignoring retention_days in some config loading paths.

Suggested change
cfg.RetentionDays = v
cfg.RetentionDays = v
} else if v, ok := config["retention_days"].(float64); ok {
cfg.RetentionDays = int(v)

Copilot uses AI. Check for mistakes.
// It requires a non-nil EventStore to function.
func NewTimelineServiceModule(name string, eventStore evstore.EventStore) *TimelineServiceModule {
logger := slog.Default()

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewTimelineServiceModule is documented as requiring a non-nil EventStore, but it doesn't validate the input. If eventStore is nil, TimelineHandler/ReplayHandler will panic on first request when they dereference the store. Add an explicit nil check and either return stub muxes / a safe no-op module, or fail fast with a clear error.

Suggested change
if eventStore == nil {
logger.Error("NewTimelineServiceModule requires a non-nil EventStore", "module", name)
panic("NewTimelineServiceModule: eventStore must not be nil")
}

Copilot uses AI. Check for mistakes.
@intel352
Copy link
Contributor Author

@copilot open a new pull request to apply changes based on the comments in this thread

Copy link
Contributor

Copilot AI commented Feb 23, 2026

@intel352 I've opened a new pull request, #139, to work on those changes. Once the pull request is ready, I'll request review from you.

…es (#139)

* Initial plan

* Apply PR review feedback: fix timeline/DLQ discovery, nil check, float64 config handling, and test fix

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
@intel352 intel352 merged commit 0fb2570 into main Feb 23, 2026
14 checks passed
@intel352 intel352 deleted the feat/issue-90-domain-services branch February 23, 2026 16:15
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.

Extract domain services into self-registering module types (feature flag pattern)

3 participants