Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save pedrovasconcellos/eab416bf666cad67463ed7cb2f36147a to your computer and use it in GitHub Desktop.

Select an option

Save pedrovasconcellos/eab416bf666cad67463ed7cb2f36147a to your computer and use it in GitHub Desktop.
Example of clean architecture using TypeScript.

Layer Mapping

1. domain/internal/domain (Go)

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).


2. app/internal/application (Go)

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/.


3. adapters/internal/adapters (Go)

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.


4. infrastructure/internal/infrastructure (Go)

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).


5. shared/internal/shared (Go)

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.


Dependency Direction: infrastructure → adapters → app → domain

┌─────────────────────────────────────────┐
│  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)   │
└─────────────────────────────────────────┘

Practical Flow Example

// ❌ 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

Comparison: TypeScript vs Go

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/

Summary

  1. Clean Architecture is not language-specific.
  2. Dependency direction must be respected in any stack.
  3. Folder names may vary, but responsibilities remain the same.
  4. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment