Innermost layer — pure business rules.
// domain/entities/Order.ts
export class Order {
private id: string;
private customerId: string;
private total: Money; // Value Object
// Pure business logic, no external dependencies
public calculateTotal(): Money { ... }
}Dependencies: none (zero external imports).
Use cases layer — orchestrates the domain.
// app/use-cases/CreateOrderUseCase.ts
import { Order } from '../../domain/entities/Order'; // ✅ Can import domain
import { IOrderRepository } from '../gateways/IOrderRepository'; // ✅ Gateways (ports)
export class CreateOrderUseCase {
constructor(
private orderRepo: IOrderRepository // Port, not implementation
) {}
async execute(request: CreateOrderRequest): Promise<CreateOrderResponse> {
const order = new Order(request.customerId, request.items);
await this.orderRepo.save(order); // Uses gateway
return new CreateOrderResponse(order.id);
}
}
// app/gateways/IOrderRepository.ts (interfaces/ports)
export interface IOrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
// app/dto/CreateOrderRequest.ts
export interface CreateOrderRequest {
customerId: string;
items: OrderItem[];
}Dependencies: can import domain/ and its own gateways/ (ports/interfaces). Cannot import adapters/ or infrastructure/.
Adapters layer — translates between application and technologies.
// adapters/http/OrderController.ts
import { CreateOrderUseCase } from '../../app/use-cases/CreateOrderUseCase'; // ✅
import { CreateOrderRequest } from '../../app/dto/CreateOrderRequest'; // ✅
import { OrderRepositoryMongo } from '../persistence/OrderRepositoryMongo'; // ✅
export class OrderController {
constructor(
private createOrderUC: CreateOrderUseCase
) {}
async handle(req: ExpressRequest, res: ExpressResponse) {
// 1. Convert HTTP to DTO
const dto: CreateOrderRequest = {
customerId: req.body.customerId,
items: req.body.items
};
// 2. Call use case
const result = await this.createOrderUC.execute(dto);
// 3. Convert DTO to HTTP response
res.json({ orderId: result.orderId });
}
}
// adapters/persistence/OrderRepositoryMongo.ts
import { IOrderRepository } from '../../app/gateways/IOrderRepository'; // ✅ Port
import { Order } from '../../domain/entities/Order'; // ✅ Domain
import { MongoClient } from '../../infrastructure/mongodb/Client'; // ✅ Infrastructure
export class OrderRepositoryMongo implements IOrderRepository {
constructor(private mongoClient: MongoClient) {} // Uses infrastructure
async save(order: Order): Promise<void> {
// Convert Order (domain) to MongoDB document
await this.mongoClient.collection('orders').insertOne({
id: order.id,
customerId: order.customerId,
// ...
});
}
}Dependencies: can import app/, domain/, and infrastructure/. Cannot define business logic.
Outermost layer — drivers and SDKs.
// infrastructure/mongodb/Client.ts
import { MongoClient as SDKClient } from 'mongodb'; // External SDK
export class MongoClient {
private client: SDKClient;
connect(uri: string): Promise<void> { ... }
collection(name: string) { ... }
}
// infrastructure/http/ExpressServer.ts
import express from 'express';
export class ExpressServer {
private app = express();
listen(port: number): void { ... }
use(middleware: any): void { ... }
}Dependencies: only external SDKs (like mongodb, express). Cannot import app/, domain/, or adapters/ (except when used by adapters).
Utilities without external dependencies.
// shared/utils/DateHelper.ts
export class DateHelper {
static formatISO(date: Date): string {
// No external dependencies
return date.toISOString();
}
}
// ❌ WRONG - cannot do this in shared
import { MongoClient } from '../infrastructure/mongodb/Client'; // VIOLATION!Dependencies: only standard language libraries. Cannot import other layers.
┌─────────────────────────────────────────┐
│ infrastructure/ │ ← Outermost layer
│ (MongoDB client, HTTP server, AWS SDK)│
└─────────────────┬───────────────────────┘
│ (used by)
↓
┌─────────────────────────────────────────┐
│ adapters/ │
│ (HTTP controllers, MongoDB repository)│ ← Implements ports
└─────────────────┬───────────────────────┘
│ (implements)
↓
┌─────────────────────────────────────────┐
│ app/ │
│ (use cases, gateways/interfaces, DTOs)│ ← Defines ports
└─────────────────┬───────────────────────┘
│ (uses)
↓
┌─────────────────────────────────────────┐
│ domain/ │ ← Innermost layer
│ (entities, value objects, services) │
└─────────────────────────────────────────┘
// ❌ WRONG - domain trying to import infrastructure
// domain/entities/Order.ts
import { MongoClient } from '../../infrastructure/mongodb/Client'; // ❌ VIOLATION!
// ❌ WRONG - app importing adapters directly
// app/use-cases/CreateOrderUseCase.ts
import { OrderRepositoryMongo } from '../../adapters/persistence/OrderRepositoryMongo'; // ❌ VIOLATION!
// Should use IOrderRepository (interface)
// ✅ CORRECT
// app/use-cases/CreateOrderUseCase.ts
import { IOrderRepository } from '../gateways/IOrderRepository'; // ✅ Interface
import { Order } from '../../domain/entities/Order'; // ✅ Domain
// adapters/persistence/OrderRepositoryMongo.ts
import { IOrderRepository } from '../../app/gateways/IOrderRepository'; // ✅ Implements interface
import { MongoClient } from '../../infrastructure/mongodb/Client'; // ✅ Uses infrastructure| Concept | TypeScript | Go |
|---|---|---|
| Domain | domain/ |
internal/domain/ |
| Application | app/ |
internal/application/ |
| Use Cases | app/use-cases/ |
internal/application/usecases/ |
| Gateways/Ports | app/gateways/ |
internal/application/interfaces/gateways/ |
| DTOs | app/dto/ |
internal/application/dto/ |
| Adapters | adapters/ |
internal/adapters/ |
| Infrastructure | infrastructure/ |
internal/infrastructure/ |
| Shared | shared/ |
internal/shared/ |
- Clean Architecture is not language-specific.
- Dependency direction must be respected in any stack.
- Folder names may vary, but responsibilities remain the same.
app/gateways/in TypeScript =internal/application/interfaces/gateways/in Go — both define ports/interfaces that adapters implement.
The final phrase "Mirror the same directional dependencies: infrastructure → adapters → app → domain" means: even when using TypeScript, maintain the same dependency direction as in the Go example.