Skip to content

ADR-010: Code Organization Conventions

Status

Accepted

Context

An audit of the 12 domain skills revealed inconsistent conventions for where code lives in the codebase:

Service directories don't match their domains:

  • LocationService lives in services/resources/ (not services/locations/)
  • WaitlistService lives in services/user/ (not services/waitlist/)
  • CreditTransferService is separated from services/credits/ into services/transfers/
  • LockoutManagementService is separated from services/scheduling/ into services/lockouts/

Test file locations follow three competing conventions:

  • Most domains: centralized under tests/ directory
  • Physical-access: co-located with source files (e.g. SecurityService.test.ts next to SecurityService.ts)
  • Credits: duplicate test paths (tests/unit/services/credits/ AND tests/services/credits/)
  • Some domains use tests/api/routes/{domain}.*.test.ts, others use tests/api/{domain}/

Webhook handler placement is inconsistent:

  • Stripe and Acuity webhooks have dedicated services (StripeWebhookService, AcuityWebhookService)
  • Auth-sync and Twilio webhooks have logic directly in route files

Decision

Service directory organization

Services live in a directory named after their domain:

  • services/locations/ (not services/resources/ for LocationService)
  • services/waitlist/ (not services/user/ for WaitlistService)
  • services/credits/ includes CreditTransferService (not separate services/transfers/)
  • services/reservations/ includes LockoutManagementService (not separate services/lockouts/)

Test file location conventions

  • All tests live under the centralized tests/ directory, not co-located with source.
  • API/integration tests: tests/api/{domain}/ (one directory per domain).
  • Unit tests: tests/unit/services/{domain}/ (single nesting level).
  • Permission tests: tests/api/{domain}/ alongside other API tests for that domain, not in a separate tests/api/routes/ directory.

Webhook handler convention

Webhook handlers that dispatch to multiple sub-handlers or contain non-trivial business logic get a dedicated service (following the StripeWebhookService and AcuityWebhookService pattern). Simple single-purpose webhooks (auth-sync, twilio incoming) may keep logic in the route file.

Alternatives considered

Co-locate tests with source. Some teams prefer __tests__/ directories next to source files. Rejected because the codebase overwhelmingly uses the centralized tests/ directory — co-located tests are the exception (only physical-access), and agents would produce inconsistent output.

Group services by layer instead of domain (e.g. services/crud/, services/webhooks/). Rejected because domain grouping matches how agents discover code — they're tasked with domain work, not layer work.

Consequences

Benefits:

  • Service directories match their domain names — agents find services where they expect them
  • Consistent test locations make it easy to find and add tests
  • Duplicate test paths eliminated

Tradeoffs:

  • Moving service files changes every import site. Each move should be its own commit.
  • Moving test files may require updating test runner configuration or glob patterns.

Risks:

  • Service directory renames must be coordinated — if services/resources/ still has other services (ResourceService, ResourceGroupService, etc.), only LocationService moves out. Don't leave an empty directory.