Skip to content

Instantly share code, notes, and snippets.

@pedrovasconcellos
Last active November 1, 2025 09:21
Show Gist options
  • Save pedrovasconcellos/175fc03e746a5679c8a1e445bd29b58d to your computer and use it in GitHub Desktop.
Save pedrovasconcellos/175fc03e746a5679c8a1e445bd29b58d to your computer and use it in GitHub Desktop.
Clean Architecture Memory Bank

Clean Architecture Memory Bank

TL;DR

  • Clean Architecture keeps business rules independent from delivery so changes in frameworks never break core logic.
  • Dependencies flow inward: frameworks → adapters → application → domain.
  • Use cases expose gateways (ports); adapters implement them; frameworks wire everything together under cmd/ entrypoints.
  • Always decide between Quick vs Task workflow (see docs/AGENT_INSTRUCTIONS.md) based on the layer surface area you touch.

Core Principles

  • Separation of concerns: Domain logic lives in entities/value objects; use cases orchestrate; adapters translate boundaries; frameworks provide tooling only.
  • Dependency rule: Inner layers know nothing about outer layers—never import adapters or frameworks from the domain or application packages.
  • Use-case centric design: Each business capability is a use case composed of interfaces, DTOs, and an implementation coordinating entities.
  • Replaceable infrastructure: Treat databases, queues, CLIs, and HTTP servers as plugins behind interfaces.
  • Testability first: Inner layers stay pure for deterministic tests; adapters get contract tests; frameworks covered by integration smoke tests.

Vocabulary

Term Meaning Where to document
Entity Business object with invariants internal/domain/entities + memory-bank/project.md
Value Object Immutable concept identified by value (Money, Address) internal/domain/valueobjects
Use Case Application-specific workflow orchestrating entities internal/application/usecases
Gateway (aka Port) Interface the use case depends on (e.g., repository, message bus) internal/application/interfaces/gateways
Adapter Implementation of a gateway for a specific technology internal/adapters/<tech>
Driver Framework entry (HTTP server, CLI, worker) cmd/<entry>

Layer Responsibilities

Layer Responsibilities Owns Depends On
Domain (Entities, Value Objects, Domain Services) Ubiquitous language, business invariants, pure logic internal/domain none
Application (Use Cases) Coordinate domain objects, enforce application policies internal/application Domain + defined gateways (ports)
Interface Adapters Translate between use cases and delivery (HTTP, queues, presenters) internal/adapters Application interfaces
Frameworks & Drivers Startup code, wiring, infrastructure SDKs cmd, internal/infrastructure Adapters via interfaces

Framework drivers such as MongoDB clients belong to this outer layer (internal/infrastructure). Repository adapters that satisfy application gateways (ports) live under internal/adapters/persistence and compose those drivers, keeping dependencies flowing inward.

Directory Reference

.                                     # Repository root
├── .github/                          # CI/CD workflows configurations
│   └── workflows/
│       ├── ci.yml                    # Test and lint pipeline
│       └── cd.yml                    # Deploy pipeline
├── docs/
│   ├── architectures/
│   │   └── clean-architectures/
│   │       └── CLEAN_ARCHITECTURE.md # You are here (agent memory)
│   ├── AGENT_INSTRUCTIONS.md         # Auto-Ops operational rules
│   └── QUICK_REFERENCE.md            # Commands and workflow cheat sheet
├── src/ (reference layout)
│   ├── cmd/                          # Entry points (executables)
│   │   ├── api/                      # HTTP server
│   │   │   └── main.go               # API server initialization
│   │   ├── worker/                   # Worker for messaging system processing
│   │   │   └── main.go               # Worker initialization
│   │   └── cli/                      # Command line tool
│   │       └── main.go               # CLI initialization
│   ├── configs/                      # Configuration files (YAML, JSON, TOML)
│   │   ├── config.yaml               # Generic configuration
│   │   ├── config.dev.yaml           # Development environment configuration
│   │   └── config.prod.yaml          # Production environment configuration
│   └── internal/                     # Internal code (not exportable)
│       ├── domain/                   # Pure domain layer
│       │   ├── entities/             # Business entities and models
│       │   │   └── order.go          # Order struct
│       │   ├── services/             # Domain Services (business rules)
│       │   │   ├── interfaces/       # Domain Service contracts
│       │   │   │   └── i_order_service_domain.go # IOrderServiceDomain contract
│       │   │   └── implementations/  # Concrete Domain Service implementations
│       │   │       └── order_service_domain.go # Concrete Domain Service
│       │   ├── valueobjects/         # Value Objects (e.g., Money, Address)
│       │   │   └── money.go          # VO for monetary values
│       │   └── events/               # (Optional) Pure domain events
│       │       └── order_created.go  # Event representing an order creation
│       ├── application/              # Use cases and logic orchestration
│       │   ├── dto/                  # Data Transfer Objects grouped by type
│       │   │   ├── requests/         # Request DTOs
│       │   │   │   ├── create_order_request.go # CreateOrder request
│       │   │   │   └── list_orders_request.go  # ListOrders request
│       │   │   └── responses/        # Response DTOs
│       │   │       ├── create_order_response.go # CreateOrder response
│       │   │       └── list_orders_response.go  # ListOrders response
│       │   ├── interfaces/           # Use case and gateway (port) contracts
│       │   │   ├── usecases/         # Use case boundaries
│       │   │   │   ├── i_create_order_usecase.go   # ICreateOrderUseCase contract
│       │   │   │   └── i_list_orders_usecase.go    # IListOrdersUseCase contract
│       │   │   └── gateways/         # Interfaces for external infrastructure
│       │   │       └── i_order_repository.go       # Repository contract
│       │   ├── usecases/             # Use Case implementations
│       │   │   ├── create_order_usecase.go   # CreateOrderUseCase implementation
│       │   │   └── list_orders_usecase.go    # ListOrdersUseCase implementation
│       ├── adapters/                 # Input/output adapters
│       │   ├── http/                 # HTTP/REST adapters
│       │   │   ├── handlers/         # Handlers invoking Use Cases
│       │   │   │   └── order_handler.go # Concrete REST handler
│       │   │   └── router.go         # HTTP routing
│       │   ├── sqs/                  # AWS SQS queue adapters
│       │   │   └── order_events.go   # SQS event processing
│       │   ├── aws_lambda/           # AWS Lambda adapters
│       │   │   └── lambda_handler.go # Lambda handler
│       │   ├── grpc/                 # gRPC adapters (optional)
│       │   │   ├── proto/            # .proto files for gRPC
│       │   │   └── server.go         # gRPC server
│       │   └── persistence/          # Repository implementations (gateways)
│       │       └── mongodb/
│       │           └── order_repository.go # Implements application gateway using Mongo client
│       ├── infrastructure/           # Concrete infrastructure implementations
│       │   ├── config/               # Configuration loader
│       │   │   └── loader.go         # Loads config.yaml files
│       │   ├── mongodb/              # MongoDB client (framework driver)
│       │   │   └── client.go         # Initializes MongoDB connection & collections
│       │   ├── aws/                  # Generic AWS SDK
│       │   │   ├── sqs/              # SQS client/wrapper
│       │   │   │   └── client.go     # Message enqueueing and consumption
│       │   │   └── s3/               # S3 client/wrapper
│       │   │       └── client.go     # Object upload/download
│       │   ├── cache/                # Cache implementation (Redis)
│       │   │   └── client.go         # Redis client
│       │   ├── logger/               # Logger configuration (Zap, Logrus…)
│       │   │   └── zap.go            # Logger initialization
│       │   └── metrics/              # Prometheus instrumentation and metrics
│       │       └── prometheus.go     # Exposes metrics
│       └── shared/                   # Cross-cutting helpers (see Shared Directory Policy)
│           ├── errors/               # Error helpers kept infrastructure-agnostic
│           │   └── error.go          # Common error definitions
│           └── utilities/            # Stateless utilities without outer-layer deps
│               └── datetime_util.go  # General helper
├── scripts/                          # Auxiliary scripts (migrations, seeds, deploy)
│   ├── migrate.sh                    # Database migrations via script
│   ├── seed.sh                       # Populate initial data
│   └── docker-entrypoint.sh          # Custom Docker entrypoint
├── migrations/                       # Versioned MongoDB migrations
│   └── 20250529_create_orders_collection.js
├── tests/                            # Automated tests
│   ├── units/                        # Unit tests
│   ├── integrations/                 # Integration tests (MongoDB, SQS)
├── Makefile                          # Build, test, lint, run shortcuts
├── Dockerfile                        # Docker image definition for the application
├── docker-compose.yml                # Service orchestration for local dev
├── README.md                         # Quick start guide and overview
└── go.mod / go.sum                   # Go module definitions and dependencies

The src/ tree above is the canonical structure agents should follow when creating new modules. Adjust paths if the concrete project uses different roots but preserve layer isolation.

Layout Notes and Conventions

  • Repository contracts and other gateways (ports) stay under the application package (internal/application/interfaces/gateways) so the domain layer remains free of external dependencies.
  • Keep internal/shared limited to neutral utilities (for example, error formatting) that do not import outer-layer packages; avoid moving domain or adapter logic into that area.
  • The tree uses Go naming and files as reference. For other stacks, keep conceptual equivalents (for example, app/domain, lib/application) while preserving dependency direction.
  • File names shown with Go conventions (main.go, i_interface.go) are illustrative; adapt extensions and naming patterns to the idioms of your stack (e.g., *.ts, *.py, dropping the i_ prefix if not used).
  • The internal/domain/events folder is optional and captures pure domain events (e.g., OrderCreated) that represent business facts without coupling to infrastructure. If the project does not use events, omit the folder entirely.

Cross-Stack Layout Example (TypeScript)

src/
├── app/                     # Application layer (use cases, DTOs, gateways)
│   ├── use-cases/           # Business orchestrators
│   ├── dto/                 # Request/response DTOs
│   └── gateways/            # Interfaces consumed by use cases
├── domain/                  # Entities, value objects, domain services
├── adapters/                # HTTP controllers, queue consumers, presenters
├── infrastructure/          # Drivers and SDK wrappers (DB clients, HTTP)
└── shared/                  # Cross-cutting helpers (see policy)

Mirror the same directional dependencies: infrastructure → adapters → app → domain.

Shared Directory Policy

  • Scope: Only cross-cutting helpers that are both stateless and infrastructure-agnostic belong under internal/shared.
  • Dependency rule: Code in shared must depend exclusively on language standard libraries or inner layers; never import adapters, infrastructure SDKs, or framework packages.
  • Promotion rule: When a helper starts requiring outer-layer details (e.g., database clients, HTTP encoders), move it to the appropriate adapter or infrastructure package instead of expanding shared.
  • Documentation: Record any intentional exceptions in the decision log to keep a traceable justification for deviations.

Disclaimer: I know that in Go it's not idiomatic to prefix interface names with 'I', but I prefer to use this pattern because it has always provided me with faster file identification when using my search of development IDE.

Component Responsibility Input/Output Accesses Repositories Contains Business Logic
Use Case (application layer internal/application/usecases) Orchestrates an end-to-end system action. Coordinates transactions, calls repositories and domain services DTOs (input and output) Yes Only minimal orchestration logic. Rich business rules live in entities and domain services
Domain Service (domain layer internal/domain/services) Encapsulates business logic that doesn’t fit well in a single entity or involves multiple aggregates Domain objects Not directly. Ideally operates on already loaded entities Yes. Pure domain business rules

Request Lifecycle Example (HTTP → MongoDB)

  1. cmd/api/main.go boots the HTTP server, builds dependencies, and registers routes.
  2. An HTTP request hits internal/adapters/http/handlers/order_handler.go.
  3. The handler maps the payload into a DTO from internal/application/dto/requests and calls the CreateOrderUseCase interface.
  4. The use case coordinates entities (internal/domain/entities/order.go) and invokes repository gateways (ports).
  5. The persistence adapter in internal/adapters/persistence/mongodb uses the infrastructure client to translate gateways (ports) into MongoDB operations.
  6. The response DTO is turned into an HTTP payload by the presenter and returned.

Asynchronous Flows and Consistency

  • Model domain-published events as pure objects (internal/domain/events) and deliver them to outbound adapters (queue, broker) through gateways defined in the application layer.
  • For inverse flows (e.g., message processing), keep the drivers under cmd/worker, translate the payload into a DTO, and call the same use case that would serve the synchronous path.
  • Prefer handling transactional consistency inside the application layer, coordinating multiple gateways around the same use case. When the backend lacks transactions, document compensations or guarantees in the decision log.
  • Capture relevant concurrency limits (locks, idempotency, deduplication) within the use case that requires them and reflect the contract in gateway tests.

Change Playbook for Agents

  1. Read conventions: Load memory-bank/project.md and check relevant sections in docs/AGENT_INSTRUCTIONS.md before touching code.
  2. Assess complexity: If the change touches only one adapter or docs → Quick path; if it modifies use cases, domain rules, or multiple layers → Task path.
  3. Define gateways first: When new infrastructure is needed, add interfaces under internal/application/interfaces/gateways before implementing adapters.
  4. Keep dependency direction: Inner layers must not import packages from outer layers. Add new DTOs or interfaces rather than pushing infrastructure inward.
  5. Update memory: Capture significant architecture decisions or deviations in this file or memory-bank/changelog.md.

Implementation Patterns

  • Entities: Keep constructors validating invariants; expose behaviour methods; avoid exporting mutable fields.
  • Use cases: Accept interfaces + DTOs; return DTOs or error objects; encapsulate transactions or orchestrations.
  • Adapters: Split handler (IO) and presenter (formatting). For queues, keep message parsing separate from use case invocation. Gateway implementations (e.g., persistence/mongodb/order_repository.go) live here and satisfy the contracts defined by the application layer.
  • Infrastructure: Provide factories to construct clients; hide SDK types behind internal structs; supply configuration via configs/*.yaml; expose low-level drivers (MongoDB client, AWS SDKs, Redis) that adapters compose.
  • cmd/: Main packages compose dependencies, inject adapters into use cases, and start servers/workers.
  • shared/: Reserve this directory for truly generic helpers (logging setup, serialization utilities) that do not import outer layers; if a helper needs to talk to adapters or infrastructure, promote it to the respective layer.

Testing Strategy

  • Unit-test entities/value objects with pure Go tests (no mocks required).
  • Use table-driven tests for use cases, mocking gateways via small in-memory doubles.
  • Adapters get contract tests ensuring they satisfy gateway expectations (e.g., repository behaves under success/failure cases).
  • End-to-end smoke tests live under tests/ or internal/integration to exercise real infrastructure sparingly.

Checklists

Before Implementing

  • Reviewed conventions (memory-bank/project.md) and workflows (docs/AGENT_INSTRUCTIONS.md).
  • Identified affected layer(s) and chosen Quick vs Task path.
  • Declared/updated required gateways and DTOs.

During Implementation

  • Dependencies flow inward only.
  • Adapters remain thin; business logic pushed to use cases or domain services.
  • Configurable values read through configuration layer, not hardcoded.

Before Requesting Review

  • Tests updated/passing per layer touched.
  • Documentation adjusted (this file, QUICK_REFERENCE, AGENT_INSTRUCTIONS if workflows shift).
  • Memory-bank context + quick changes recorded where required.

Anti-Patterns to Avoid

  • Entities importing infrastructure packages (violates dependency rule).
  • Use cases returning raw infrastructure types (e.g., Mongo models or HTTP responses).
  • Handlers performing business logic instead of invoking use cases.
  • Configuration or credentials hard-coded in adapters or domain logic.
  • Skipping gateway definitions (ports), causing tight coupling between use cases and adapters.

Decision Log

Date Decision Notes
2025-02-03 Adopt Clean Architecture layering for AI agents Canonical structure captured in this memory bank
2025-02-04 Auto-Ops workflows govern change process Quick vs Task selection tied to layer scope

Update the table when major architectural decisions or exceptions are made.

Open Questions

  • Document deviations if the real project structure diverges from the canonical tree.
  • Record integration-specific guidelines (e.g., message schemas, API contracts) once stabilized.
  • Clarify concurrency and transaction boundaries for domain services when first implemented.

Related References

  • docs/AGENT_INSTRUCTIONS.md — automation workflow and mode selection
  • docs/QUICK_REFERENCE.md — shortcut commands and complexity matrix
  • memory-bank/project.md — project-specific conventions and terminology
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment