Skip to content

refactor: move business logic from listeners into services #14

@rorybyrne

Description

@rorybyrne

Summary

Several EventListeners contain business logic directly instead of delegating to Services. This violates our documented architecture pattern (CLAUDE.md Section 6) where entry points should be thin adapters that delegate to Services.

Current State

The following listeners contain business logic directly:

IngestFromUpstream (ingest_listener.py:26-78)

  • Iterates over ingestor results
  • Creates DepositionSRN
  • Emits events to outbox
  • Should delegate to an IngestService

ConvertDepositionToRecord (record_creation_listener.py:26-61)

  • Creates Record aggregate
  • Persists via repository
  • Emits RecordPublished event
  • Should delegate to a RecordService

ProjectNewRecordToIndexes (index_projector.py:17-27)

  • Iterates over index backends
  • Calls backend.ingest() directly
  • Should delegate to an IndexService

Target Pattern

# Before (logic in listener)
class IngestFromUpstream(EventListener[IngestRequested]):
    ingestors: IngestorRegistry
    outbox: Outbox
    config: Config

    async def handle(self, event: IngestRequested) -> None:
        # 50+ lines of business logic...

# After (thin listener delegating to service)
class IngestFromUpstream(EventListener[IngestRequested]):
    service: IngestService

    async def handle(self, event: IngestRequested) -> None:
        await self.service.run_ingest(
            ingestor_name=event.ingestor_name,
            since=event.since,
            limit=event.limit,
        )

Benefits

  1. Testability - Services can be unit tested without event infrastructure
  2. Reusability - Services can be called from multiple entry points (listeners, commands, CLI)
  3. Consistency - Matches the documented architecture pattern
  4. Separation of concerns - Entry points handle event wiring, services handle business logic

Tasks

  • Create IngestService in domain/ingest/service/
  • Refactor IngestFromUpstream to delegate to IngestService
  • Create RecordService in domain/record/service/
  • Refactor ConvertDepositionToRecord to delegate to RecordService
  • Create IndexService in domain/index/service/
  • Refactor ProjectNewRecordToIndexes to delegate to IndexService
  • Update DI providers for new services
  • Add unit tests for services

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions