NJ Municipality Lookup
CodebaseSrc

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 names
  • Address - Validated address input with normalization
  • GeocodingResult - Geocoding response with municipality info
  • CacheEntry - Cache storage with TTL and access tracking
  • BulkJob - Batch geocoding operation with progress tracking
  • HealthStatus - System health monitoring status

Use Cases

Single-responsibility functions that orchestrate business logic:

  • lookupAddress() - Geocode an address and return municipality
  • getSuggestions() - Get address autocomplete suggestions

Ports (Interfaces)

Contracts that adapters must implement:

  • GeocodingServicePort - Geocoding operations
  • SuggestionsServicePort - Address autocomplete
  • CacheServicePort - Caching operations
  • HttpClientPort - HTTP requests
  • LoggerPort - Logging operations

Errors

Typed errors extending a base error factory pattern:

  • ValidationError - Input validation failures
  • AddressNotFoundError - No geocoding results found
  • ApiTimeoutError - External API timeouts
  • InvalidApiResponseError - Malformed API responses
  • CacheError - Cache operation failures
  • InvalidAddressError - 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/ or components/ (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.

On this page