Skip to content

ADR-009: Domain Service vs Application Service Classification

Status

Approved - Implemented

Context

The SyRF.ProjectManagement.Core library historically contained both domain logic and application-layer orchestration in the same project. This blurred the DDD boundary between services that encode business rules and services that merely coordinate data access. During the refactoring of StageReviewService (PR #2543), we needed explicit rules to determine what belongs in the domain layer vs the application layer, and where each should live in the project structure.

Decision

Classification Rules

A service is a Domain Service if it meets ANY of these criteria:

  1. Domain Expert Recognition: A domain expert would recognise the operation as part of their workflow, not just as "getting data"
  2. Ordering Invariants: Removing or reordering steps would violate a domain invariant or produce incorrect business behaviour
  3. Business Decisions: The service makes decisions based on domain rules (not just routing data between components)
  4. Cross-Aggregate Logic: The logic spans multiple aggregates but doesn't naturally belong to any single entity

A service is an Application Service if ALL of these are true:

  1. No Domain Decisions: The service makes no business decisions — it loads, delegates, and returns
  2. No Ordering Constraints: Steps could be reordered or removed without violating domain rules
  3. Substitutable by Caller: The controller could call the repository directly without losing any business logic
  4. Infrastructure Coordination: Its purpose is to bridge between external concerns (HTTP, messaging) and the domain

The Grey Area Test

When a service is ambiguous, ask: "If I inlined this service into the controller, would I be putting domain logic in the controller?"

  • If yes → it's a domain service (the sequencing/validation IS the domain logic)
  • If no → it's an application service (the controller could do this directly)

Project Location

Layer Project Contains
Domain SyRF.ProjectManagement.Core Entities, Value Objects, Domain Services, Domain Events, Repository interfaces
Application SyRF.ProjectManagement.Application Application Services that orchestrate domain operations without encoding business rules
Infrastructure SyRF.ProjectManagement.Mongo.Data Repository implementations, database-specific code

The Application project: - References Core (follows the dependency rule: application → domain) - Is referenced by API Endpoint and PM Endpoint (hosts depend on application layer) - Does NOT reference infrastructure (Mongo.Data) — it uses repository interfaces from Core

Classification of Review Services

Applying these rules to the study review domain:

Service Classification Reason Project
StudyAssignmentPolicy Domain Service Pure business rules for study assignment. No I/O. Domain expert would recognise the decision matrix. Core
StageReviewService Domain Service (workflow) GetRandomStudyAsync encodes domain-meaningful sequencing: validate project state → validate stage → determine assignment → fetch study. Inlining into controller would put domain validation in the presentation layer. Core
ReviewStatsQueryService Application Service All methods are load-delegate-return with no domain decisions. Controller could call the repository directly. Application
Stage.HasReachedMaxInProgress Entity Behaviour Domain logic on the entity that owns the configuration (MaxInProgress, HideExcludedStudiesFromReviewers). Core (on entity)

Value Objects (always Domain)

StudyAssignment, StudyFetchStrategy, ReviewStatus, StudyReviewStatus, PoolSwitchStatus, ReviewStatusInfo — all domain vocabulary, remain in Core.

Consequences

Positive

  • Compile-time boundary enforcement: Application services cannot accidentally depend on infrastructure, and domain code cannot depend on application concerns
  • Clear placement rules: New services can be classified using the documented criteria
  • Testability: Domain services tested without infrastructure mocks; application services tested with simple mock setups
  • Discoverability: Developers know where to find business logic (Core) vs orchestration (Application)

Negative

  • Additional project: One more .csproj in the solution increases build graph complexity slightly
  • Migration effort: Existing application services in Core would need to be migrated over time (not all at once)

Neutral

  • The rules are guidelines, not absolutes. Edge cases should be discussed and the decision documented
  • Services can be reclassified if their responsibilities change (e.g., if orchestration gains domain logic, promote to domain service)

References

  • Evans, Eric. Domain-Driven Design, Chapter 5: "Services"
  • Vernon, Vaughn. Implementing Domain-Driven Design, Chapter 7: "Application Services"
  • PR #2543: Initial implementation of this separation