Domain
The domain layer contains pure business logic with zero dependencies on external frameworks or libraries. This is the heart of the application following Clean A
Domain Layer (domain/)
The domain layer contains pure business logic with zero dependencies on external frameworks or libraries. This is the heart of the application following Clean Architecture principles.
Purpose
This layer defines:
- What the application does (business rules)
- Not how it's implemented (infrastructure details)
The domain is framework-agnostic and could theoretically work with any UI framework, database, or external service.
Structure
entities/- Core business objects (Municipality, Address, GeocodingResult, CacheEntry, BulkJob, HealthStatus)use-cases/- Application business logic (lookup-address, get-suggestions)ports/- Interfaces for external dependencies (repository pattern)errors/- Domain-specific error types (ValidationError, AddressNotFoundError, etc.)
Key Concepts
Entities
Immutable business objects with validation logic and factory functions:
Municipality- Branded type for NJ municipality namesAddress- Validated address input with normalizationGeocodingResult- Geocoding response with municipality infoCacheEntry- Cache storage with TTL and access trackingBulkJob- Batch geocoding operation with progress trackingHealthStatus- System health monitoring status
Use Cases
Single-responsibility functions that orchestrate business logic:
lookupAddress()- Geocode an address and return municipalitygetSuggestions()- Get address autocomplete suggestions
Ports (Interfaces)
Contracts that adapters must implement:
GeocodingServicePort- Geocoding operationsSuggestionsServicePort- Address autocompleteCacheServicePort- Caching operationsHttpClientPort- HTTP requestsLoggerPort- Logging operations
Errors
Typed errors extending a base error factory pattern:
ValidationError- Input validation failuresAddressNotFoundError- No geocoding results foundApiTimeoutError- External API timeoutsInvalidApiResponseError- Malformed API responsesCacheError- Cache operation failuresInvalidAddressError- Security validation failures (XSS, injection)
Dependency Rules
✅ Allowed:
- Import from other domain modules
- Import from
lib/(shared utilities) - Use standard TypeScript/JavaScript
❌ Forbidden:
- Import from
adapters/(implementations) - Import from
app/orcomponents/(UI) - Import from external frameworks (Next.js, React, etc.)
- Direct API calls or database access
Examples
// Use case with injected dependencies
import { lookupAddress } from "./domain/use-cases/lookup-address";
import { geocodingClient } from "./adapters/nj-api/geocoding-client";
import { cache } from "./adapters/cache/in-memory-cache";
const result = await lookupAddress(
addressInput,
geocodingClient, // Port implementation injected
cache, // Port implementation injected
);
Testing
Domain logic is tested in isolation with no external dependencies. Tests use fake implementations of ports to verify business logic independently of infrastructure.