DDD / Hexagonal Architecture (youteacher_web)
youteacher_web is the Next.js 15 / React 19 front end of YouTeacher, and it is organised as a hexagonal (ports-and-adapters) codebase with a domain-driven core. The point of the arrangement is simple: business rules never learn where the data comes from, and the data plumbing never learns the business rules. Everything else follows from keeping those two apart.
Four layers, one direction of dependence
The src/ tree splits into four layers, and dependencies only ever point inward:
domain/— pure business logic with no framework imports. It holds theAccountaggregate and its value objects (email, password, TOTP code, OAuth provider link) plus thejobsentities. Domain objects are immutable: a change returns a new instance rather than mutating in place (for example, linking a provider yields a freshAccount).application/— use cases and ports. A use case such asLoginUseCaseorchestrates domain logic; a port is an interface the use case depends on, never a concrete class. The ports are deliberately narrow —LoginGatewayis justlogin/logout/fetchProfile,StorageGatewayis justreadUser/persistSession/clearSession— so a use case declares exactly the capabilities it needs and nothing more.infrastructure/— the adapters that implement those ports against the real world: HTTP clients to the backend services, the auth API gateways, browser storage, recaptcha, navigation.AuthApiGateway implements AuthGatewayby delegating to the transport functions; the use case above it never seesfetch.features/— self-contained UI modules (auth, job-search) with their own components, hooks and utilities.
Dependency injection lives in a React hook
The interesting move is where the concrete adapters get bound to the abstract use cases. There is no DI container; the composition root is a client hook, useAuthService. It instantiates the concrete gateways once (memoised), then constructs each use case by handing it its ports — new LoginUseCase(authGateway, recaptcha, storage) — and returns the ready-wired use cases to the components. The feature layer is therefore the only place that knows both the interface and its implementation; the domain and application layers stay ignorant of both React and the network.
This is what makes the code testable without mocking the world: a unit test constructs the same use case with fake ports (login: vi.fn(), a stub storage) and asserts the orchestration — that recaptcha ran, that the gateway was called with the captcha token, that the session was persisted. Swapping the real HTTP adapter for a test double changes nothing in the use case.
The auth bounded contexts
The auth domain is documented as five bounded contexts that all compose through the single Account aggregate: Identity (the account itself), Access (session adoption/revocation), Credentials (password rules), MFA (TOTP / WebAuthn state), and Social Linking (OAuth provider links). Transport DTOs are mapped into these domain types at the boundary, so the wire format of any one backend service never leaks inward. One design detail worth naming at the architecture level: the session itself is held server-side in an httpOnly cookie, and the client persists only non-secret user info locally.
Related
- authentication-and-sessions — the auth subsystem end to end
- backend-service-integration — how the app reaches its backend services
- talent-search-recruitment-flow — the recruiter-facing feature built on this spine