Pure domain logic, completely framework-free.
Structure
bounded-contexts/{bc-name}/
├── bootstrap/ ← Dependency wiring (at BC root!)
│ ├── index.ts ← Main orchestration
│ ├── adapters.bootstrap.ts
│ ├── use-cases.bootstrap.ts
│ ├── validators.bootstrap.ts
│ ├── controller.bootstrap.ts
│ └── routes.bootstrap.ts
├── app/
│ ├── ports/
│ │ ├── inbound/
│ │ │ ├── {resource}/ ← Ports grouped by resource
│ │ │ │ ├── create-{resource}.command.port.ts
│ │ │ │ ├── get-{resource}.query.port.ts
│ │ │ │ └── list-{resources}.query.port.ts
│ │ └── outbound/ ← Repository interfaces
│ └── use-cases/
│ └── {resource}/ ← Use cases grouped by resource
│ ├── create-{resource}.command.ts
│ ├── get-{resource}.query.ts
│ └── list-{resources}.query.ts
├── domain/
│ ├── aggregates/
│ │ └── {aggregate}/
│ │ └── policies/
│ ├── entities/
│ ├── value-objects/
│ ├── events/ ← Domain events
│ ├── services/ ← Domain services
│ └── exceptions/
├── infra/
│ └── outbound-adapters/ ← Port implementations
│ └── persistence/
│ └── drizzle/
│ ├── mappers/ ← Domain ↔ persistence mappers
│ └── {resource}/ ← Repository implementations
└── presentation/ ← BC-specific HTTP layer
└── http/
├── service.metadata.ts
├── resources/ ← Resource metadata
├── endpoints/ ← Endpoint metadata
└── {resource}/{endpoint}/
├── dtos.ts ← Request/Response DTOs
├── schemas.ts ← Validation schemas
├── mappers.ts ← DTO transformations
└── endpoint.metadata.ts
Info:
The bootstrap folder is at the BC root, not under presentation. See Bootstrap Pattern for details.
Golden Rules
- NO framework imports
- NO environment variables
- NO direct cross-BC imports
- CAN import from domain (VOs, aggregates) into infra
Inbound Ports
Interfaces for use cases. Implemented by queries and commands.
interface FindUserByIdQueryInboundPort {
execute(input: FindUserByIdInputDto): Promise<FindUserByIdOutputDto>;
}
Naming Conventions
Two patterns are acceptable:
Full naming (explicit):
| Type | Interface | File |
|---|---|---|
| Query | {Name}QueryInboundPort | {resource}/{name}.query.port.ts |
| Command | {Name}CommandInboundPort | {resource}/{name}.command.port.ts |
Short naming (concise):
| Type | Interface | File |
|---|---|---|
| Query | {Name}Port | {resource}/{name}.query.port.ts |
| Command | {Name}Port | {resource}/{name}.command.port.ts |
Info:
Choose one convention and use it consistently throughout your project. The short naming works well when file names already indicate the type (.query.port.ts vs .command.port.ts).
Outbound Ports
Interfaces for external dependencies. Implemented by infrastructure.
interface UserRepositoryOutboundPort {
findById(id: UserId): Promise<User | null>;
save(user: User): Promise<void>;
}
Use Cases
Implement inbound ports using BaseInboundAdapter for automatic error handling.
import { BaseInboundAdapter, NotFoundError } from '@cosmneo/onion-lasagna/backend/core/onion-layers';
class FindUserByIdQuery
extends BaseInboundAdapter<FindUserByIdInputDto, FindUserByIdOutputDto>
implements FindUserByIdQueryInboundPort
{
constructor(private readonly userRepo: UserRepositoryOutboundPort) {
super();
}
protected async handle(input: FindUserByIdInputDto): Promise<FindUserByIdOutputDto> {
const user = await this.userRepo.findById(UserId.create(input.data.userId));
if (!user) {
throw new NotFoundError({ message: `User ${input.data.userId} not found` });
}
return FindUserByIdOutputDto.create({
id: user.id.value,
email: user.email,
name: user.name,
});
}
}
Note: Implement handle() (protected) instead of execute(). The BaseInboundAdapter provides execute() with automatic error wrapping.
Domain
Aggregates
class UserAggregate {
private constructor(
private readonly _id: UserId,
private _email: string,
private _name: string,
) {}
static create(data: CreateUserData): UserAggregate { ... }
static reconstitute(data: UserData): UserAggregate { ... }
}
Value Objects
class UserId {
private constructor(private readonly _value: string) {}
static create(value: string): UserId { ... }
static generate(): UserId { ... }
get value(): string { return this._value; }
}
See Aggregates and Value Objects for details.