Receber por email
← Voltar para o catálogo

backend-standards

CMDO architecture standards for Node.js/TypeScript backends with strict layer separation.

36estrelas
Atualizado há 8 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add LiorCohen/sdd

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/backend-standards/svg)](https://www.skillteca.com.br/skills/backend-standards)

Skills relacionadas

Ver todas de DevOps e Infra →

Alerta por categoria

Receba novas skills de DevOps e Infra toda segunda

1 email curto, só com as skills novas de DevOps e Infra. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Backend Standards Skill

CMDO ("Commando") architecture for Node.js/TypeScript backends with strict separation between infrastructure and domain concerns.

Architecture: CMDO (Controller Model DAL Operator)

Operator → Controller → Model Use Cases
   ↓            ↓              ↑
Config → [All layers] → Dependencies (injected by Controller)
                               ↓
                             DAL

Key Distinction: Infrastructure vs Domain

| Layer | Knows About | Example | |-------|-------------|---------| | Operator | Infrastructure only | "Here's a DB connection and generic HTTP client" | | Config | URLs and settings | paymentGatewayUrl: "https://api.stripe.com" | | Controller | Domain concerns | "Use httpClient + paymentGatewayUrl to charge a card" | | Model | Business logic only | "Calculate total, then call chargePayment()" |

Key Principle: Operator provides raw I/O capabilities (generic HTTP client, cache client, DB connection) with NO domain knowledge. Controller combines I/O + config to create domain-specific operations.

Layer Overview

| Layer | Path | Responsibility | |-------|------|----------------| | Entry Point | src/index.ts | Bootstrap only (ONLY file with side effects) | | Operator | src/operator/ | Raw I/O capabilities (DB, HTTP clients, cache), lifecycle management, state machine | | Config | src/config/ | Environment parsing, validation, type-safe config | | Controller | src/controller/ | Request/response handling, combines I/O + config for domain calls, creates Dependencies for Model | | Model | src/model/ | Business logic (definitions + use-cases), receives Dependencies | | DAL | src/dal/ | Data access, queries, mapping DB ↔ domain objects |

Entry Point: src/index.ts

Rules:

  • src/index.ts is the ONLY file that runs code on import (exception to the "index.ts exports only" rule for application entry points)
  • All other files export functions/types with NO side effects when imported
  • NO logic beyond importing and starting the operator
  • NO configuration loading, validation, or setup logic
// src/index.ts - Entry point (exception to index.ts rule for entry points)
import { createOperator } from "./operator";
import { loadConfig } from "./config";

const main = async (): Promise<void> => {
    const config = loadConfig();
    const operator = createOperator({ config });
    await operator.start();
};

main().catch((error) => {
    console.error("Failed to start operator:", error);
    process.exit(1);
});

Layer 1: Operator

Provides raw I/O capabilities and orchestrates application lifecycle. NO domain knowledge - only infrastructure.

What Operator Does:

  • Creates database connection pool (raw I/O capability)
  • Creates generic HTTP client (for external calls - if needed)
  • Creates cache client (if needed)
  • Binds DAL functions with database client
  • Creates Controller, passing raw I/O + DAL + config
  • Manages lifecycle via state machine (IDLE → STARTING → RUNNING → STOPPING → STOPPED)
  • Manages HTTP server for API endpoints
  • Manages lifecycle probes on separate port (health/readiness for Kubernetes)
  • Handles Unix signals for graceful shutdown (SIGTERM, SIGINT, SIGHUP)
  • Initializes telemetry (logger, metrics) before other modules

What Operator Does NOT Do:

  • NO domain-specific calls (e.g., "call payment gateway", "fetch user from auth service")
  • NO business logic
  • NO knowledge of what the raw I/O will be used for

Lifecycle State Machine:

IDLE → STARTING:PROBES → STARTING:DATABASE → STARTING:HTTP_SERVER → RUNNING
                                                                        ↓
STOPPED ← STOPPING:PROBES ← STOPPING:DATABASE ← STOPPING:HTTP_SERVER ←─┘

Unix Signal Handling:

| Signal | Source | Action | |--------|--------|--------| | SIGTERM | Kubernetes pod termination, kill command | Graceful shutdown | | SIGINT | Ctrl+C from terminal | Graceful shutdown | | SIGHUP | Terminal hangup | Graceful shutdown |

When a signal is received:

  1. Log the signal with info level
  2. Initiate graceful shutdown via stop()
  3. Wait for all connections to drain
  4. Exit with code 0 (success) or 1 (error)

Structure:

src/operator/
├── create_operator.ts      # Main factory, state machine, lifecycle
├── create_database.ts      # Database connection pool
├── create_http_server.ts   # HTTP server wrapper
├── lifecycle_probes.ts     # Health/readiness endpoints (separate port)
├── state_machine.ts        # Generic state machine implementation
├── logger.ts               # Pino logger with OpenTelemetry
├── metrics.ts              # OpenTelemetry metrics
└── index.ts                # Exports only

Layer 2: Config

Environment parsing, validation, type-safe config objects.

CRITICAL: Use dotenv for ALL environment variable access. Direct process.env access is FORBIDDEN outside the Config layer.

Environment Variable Rules:

  1. dotenv is mandatory: Always use dotenv.config() inside loadConfig() (not at module level)
  2. Config layer ONLY: process.env access is ONLY allowed in src/config/
  3. Type-safe access: All other layers receive typed Config object
  4. Validation required: Validate required vars and throw if missing
  5. Default values: Provide sensible defaults for optional vars
  6. NO direct access elsewhere: NEVER use process.env in Operator, Controller, Model, or DAL layers

What it does NOT contain: Business logic, database queries.

Structure:

src/config/
├── index.ts                # loadConfig() function, Config type
└── validation.ts           # Optional: validation helpers

Layer 3: Controller

Request/response handling, combines I/O + config for domain-specific operations, creates Dependencies object for Model.

Controller's Key Role:

  • Receives raw I/O capabilities from Operator (generic httpClient, cache, etc.)
  • Receives Config with URLs and settings
  • Combines I/O + config to create domain-specific operations (e.g., httpClient + config.paymentUrl → chargePayment)
  • Creates Model Dependencies by stitching together DAL + domain operations

HTTP Handlers: Each file in http_handlers/ corresponds to an API namespace (e.g., /users, /orders) and exports a router. The create_controller.ts imports these routers and wires them together with the Dependencies object for Model use-cases.

Handler Naming: Use operationId from OpenAPI spec with handle prefix (e.g., createUser → handleCreateUser).

Health Check Endpoints: Lifecycle probes (/health, /readiness) are handled by Operator on a separate port, NOT in the Controller.

What it does NOT contain: Database queries, business logic (delegates to Model).

Structure:

src/controller/
├── http_handlers/          # One file per API namespace
│   ├── users.ts            # Exports usersRouter
│   ├── orders.ts           # Exports ordersRouter
│   └── index.ts            # Re-exports all routers
├── create_controller.ts    # Assembles routers, creates Dependencies for Model
└── index.ts                # Exports only

Layer 4: Model

Business logic via definitions + use-cases. Model never imports from outside its module.

Definitions Rules:

  • Use TypeScript types only (type or interface)
  • NO Zod, Yup, io-ts, or similar validation libraries
  • Validation belongs in the Controller layer (input) or Operator layer (middleware)
  • Definitions are compile-time constructs, not runtime validators

Use Case Rules:

  • One use-case per file
  • Each use-case receives Dependencies as first argument
  • Return discriminated unions for success/failure
// ✅ GOOD: Discriminated union results
type CreateUserResult =
    | { readonly success: true; readonly user: User }
    | { readonly success: false; readonly error: 'email_exists' | 'inval
Compartilhar X LinkedIn