Skip to content

ADR-011: One Use Case per File ​

Status ​

Accepted

Context ​

The notification service grouped seven application operations in internal/usecase/notification.go. Sending notifications, retrieving them, and managing templates required navigating and editing the same file. The identity service already exposes its operations through separate use-case files.

We need a consistent convention that makes an operation easy to locate and keeps unrelated changes independent.

Decision ​

Each application use-case implementation file under services/<service>/internal/usecase/ contains exactly one use case.

  • Name the file after the operation in snake_case, such as send_notification.go, get_notification.go, or update_template.go.
  • Keep the use-case type, constructor, Execute method, and helpers used only by that operation together in its file.
  • Keep operation-specific DTOs beside their use case. Shared DTOs and supporting types may live in a clearly named contracts file; supporting files must not group multiple use-case implementations.
  • Keep related tests in <operation>_test.go when they exercise one use case. Tests spanning several operations may use a descriptive integration-test name.
  • Retain the same Go package. Splitting files does not require introducing a package or module per operation, or changing exported APIs and runtime behavior.

Apply this convention to new use cases and when refactoring existing use-case files. This decision does not require an immediate rewrite of every service.

The notification service now uses:

text
internal/usecase/
├── send_notification.go
├── send_notification_test.go
├── get_notification.go
├── list_notifications.go
├── create_template.go
├── list_templates.go
├── get_template.go
└── update_template.go

The existing CreateTemplateDTO remains in create_template.go and is also used by UpdateTemplateUseCase, preserving the existing contract during this refactor.

Rationale ​

Operation names map directly to filenames. Keeping each operation's dependencies and behavior together makes code easier to review and reduces conflicts between changes to unrelated operations. Retaining the package preserves straightforward dependency injection and access to shared contracts.

Consequences ​

Positive ​

  • Use cases are easier to find, review, and maintain independently.
  • Constructors and execution logic remain close to their operation.
  • File organization is consistent across service boundaries.

Negative ​

  • Use-case directories contain more files.
  • Shared contracts and helpers need deliberate placement to avoid duplication.

Documentation follows the source. Planned contracts are marked explicitly.