Skip to content

Instantly share code, notes, and snippets.

@pedrovasconcellos
Last active November 7, 2025 02:09
Show Gist options
  • Select an option

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

Select an option

Save pedrovasconcellos/068354bbbde9a4d6957150056ab2a138 to your computer and use it in GitHub Desktop.
Clean Architecture folder structure

Clean Architecture

Folder structure

.                                     # 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)
│   ├── AGENTS.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
│       │       ├── interfaces/       # Domain Events contracts
│       │       └── order_created.go  # Event representing an order creation
│       ├── application/              # Use cases and logic orchestration
│       │   ├── dto/                  # Data Transfer Objects grouped by type
│       │   │   ├── inputs/           # Processing inputs data
│       │   │   │   ├── create_order_request.go # CreateOrder request
│       │   │   │   └── list_orders_request.go  # ListOrders request
│       │   │   └── outputs/          # Processing output data
│       │   │       ├── 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
│       │   │   └── ports/            # Interfaces for external infrastructure (Adapter or Infrastructure layer)
│       │   │       └── 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
│       │   │   ├── dtos/             # Data Transfer Objects grouped by type
│       │   │   │   ├── request/      # Requests
│       │   │   │   └── responses/    # Responses 
│       │   │   ├── handlers/         # Handlers invoking Use Cases
│       │   │   │   └── order_handler.go # Concrete REST handler
│       │   │   └── router.go         # HTTP routing
│       │   ├── sqs/                  # AWS SQS queue adapters
│       │   │   ├── dtos/             # Data Transfer Objects grouped by type
│       │   │   │   ├── request/      # Requests
│       │   │   │   └── responses/    # Responses 
│       │   │   └── order_events.go   # SQS event processing
│       │   ├── aws_lambda/           # AWS Lambda adapters
│       │   │   ├── dtos/             # Data Transfer Objects grouped by type
│       │   │   │   ├── request/      # Requests
│       │   │   │   └── responses/    # Responses 
│       │   │   └── lambda_handler.go # Lambda handler
│       │   ├── grpc/                 # gRPC adapters (optional)
│       │   │   ├── dtos/             # Data Transfer Objects grouped by type
│       │   │   │   ├── request/      # Requests
│       │   │   │   └── responses/    # Responses 
│       │   │   ├── proto/            # .proto files for gRPC
│       │   │   └── server.go         # gRPC server
│       │   └── persistence/          # Repository implementations (gateways). A gateway is the concrete implementation of a port.
│       │       ├── dtos/             # Data Transfer Objects grouped by type
│       │       │   ├── request/      # Requests
│       │       │   └── responses/    # Responses 
│       │       └── 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)
│       │   │   ├── schemas/          # Database schema definitions (MongoDB document structures)
│       │   │   └── 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

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 development IDE.

Brief description of layers:

  • .github/workflows/: CI/CD pipelines for lint, tests, and deployment.
  • docs/: architecture, APIs, and deployment documentation.
  • cmd/: application executables (e.g., HTTP server, workers, CLI).
  • configs/: configuration files (YAML, JSON).
  • internal/domain/: entities and abstract interfaces with no external dependencies.
  • internal/application/: use cases and business logic orchestration.
  • internal/adapters/: input/output adapters (HTTP, SQS events, Lambda, Database Repository classes).
  • internal/infrastructure/: concrete implementations of clients (MongoDB, AWS SQS, AWS S3, Logger).
  • shared/: general-purpose libraries and utilities.
  • scripts/: auxiliary scripts for database operations and deployment.
  • migrations/: versioned scripts to create/alter collections in MongoDB.
  • test/: separates integration and unit tests.
  • Makefile: development workflow conveniences.
  • Dockerfile and docker-compose.yml: containerization and service orchestration.
  • go.mod/go.sum: project dependencies and versioning.

Why do we have the adapters folder?

  • Protocol isolation: Translates requests (HTTP, SQS, etc.) into the formats expected by use cases without contaminating application with transport details.
  • Dependency rule: Outer layers (interfaces) depend on inner layers (application and domain), but not vice versa.
  • Flexibility: Allows exposing the same use cases via multiple channels (REST, CLI, Lambda) by creating new adapters without touching business logic.
  • Separation of responsibilities: Each layer focuses on its responsibility: pure domain, use case orchestration, and I/O adaptation.
  • Testability: Well-isolated adapters make it easier to write unit tests for use cases and domain services by mocking inputs/outputs without raising real HTTP servers, queues, or connections.

What does the cmd/ folder mean?

The cmd/ convention groups the application entry points. Each subfolder represents a distinct executable:

cmd/
├── api/
│   └── main.go
├── worker/
│   └── main.go
├── cli/
│   └── main.go

In the main.go of cmd/api, for example (example for aws lambda):

package main

import (
    "net/http"
    "myproject/internal/interfaces/http"
    "myproject/internal/infrastructure/config"
    "myproject/internal/infrastructure/logger"
)

func main() {
    cfg := config.Load()
    log := logger.New(cfg.LogLevel)
    router := http.NewRouter(log, cfg)
    
    log.Info("Starting API on port: ", cfg.Port)
    http.ListenAndServe(":"+cfg.Port, router)
}
  • Responsibility: Assemble the application, load configurations, initialize clients (MongoDB, SQS, S3), instantiate handlers, and start the server or worker.
  • Inverted dependency: cmd/ imports internal layers, but they must not import cmd/, keeping business logic independent of how it’s executed.

Question 1: How do you choose what becomes an executable under cmd/ and what is just a script under scripts/?

Answer:

cmd/

  • Contains Go executables that are part of the main application.
  • Each folder in cmd/ (for example api/, worker/, cli/) has a main.go that compiles to an independent binary.
  • We use these executables to start services that need error handling, logging, centralized configuration, and dependency injection via Go.

scripts/

  • Are automation scripts (bash, PowerShell, etc.) for operational tasks: database migration, data seeding, container entrypoints, etc.
  • We do not compile scripts into Go binaries; they remain in the repository as simple command-line utilities that orchestrate other commands or executables.
  • Ideal for one-off tasks or tasks protected by CI/CD, without needing to embed domain logic or Go dependencies.

Question 2: There is an SQS in adapters/ and another in infrastructure/, what would be the difference?

Answer:

To make it clearer, let’s think in terms of responsibilities, dependencies and testing:

1. SQS Adapter (internal/adapters/sqs/order_events.go)

Main responsibility:

  • Connect the external world to the domain. In other words, listen to queues, convert messages into domain objects and trigger use cases.

What it does:

  • Receives the raw payload (JSON, XML or other) coming from SQS.
  • Parses that payload into a DTO (or message struct) and maps it to the input of the use case.
  • Validates syntax and format (valid JSON, basic required fields, types, max size).
  • Rejects invalid messages (log + send to dead-letter or ack/nack according to infra policy).
  • Handles infrastructure errors (SQS connection, timeouts, retries, backoff) — coordination of ack/nack and DLQ.
  • Calls the use case passing the mapped DTO/inputs, and handles the use case responses (success, domain error, etc).

What it does not do

  • Does not implement complex business rules (for example: "an order cannot enter state X if…"). Those validations belong to the use case / entities / domain services.
  • Should not access domain details directly beyond the ports.

What are “ports”?

In the context of Clean Architecture / Hexagonal Architecture, ports are interfaces that define contracts between layers — the system’s entry and exit points.

  • Input ports: interfaces that represent use cases / application services. E.g.: CreateOrderUseCase.Execute(input).
  • Output ports: interfaces that the application layer expects the infrastructure to implement. E.g.: OrderRepository.Save(order) or NotificationSender.Send(evt).

Ports describe what should be done, without saying how. Concrete implementations (AWS SQS, database, HTTP, etc.) live in adapters.

Dependencies

  • Can (and usually should) use the AWS SDK / SQS client and retry/backoff configurations — that is infra. The adapter lives in the Frameworks & Drivers layer, so depending on the SDK is expected. What it must not do is leak this dependency to the domain.

2. SQS Infrastructure (internal/infrastructure/aws/sqs/client.go)

Main responsibility:

  • Communicate with AWS SQS in a generic, reusable way, without business logic.

What it does:

  • Creates and configures the AWS sqs.Client (session, region, credentials).
  • Implements low-level methods:
func (c *SQSClient) SendMessage(queueURL string, body string) error { … }
func (c *SQSClient) ReceiveMessages(queueURL string, max int) ([]*sqs.Message, error) { … }
func (c *SQSClient) DeleteMessage(queueURL, receiptHandle string) error { … }
  • Manages retry policies, timeouts, Prometheus metrics and technical logs (latency, network errors).
  • Provides technical errors (timeouts, rate limits) so the adapter can decide business handling.

Dependencies:

  • Only the AWS SDK (github.com/aws/aws-sdk-go-v2/service/sqs), a generic logger and a metrics library.
  • Does not know use cases, domain DTOs or business rules.

How they integrate

Startup (cmd/worker/main.go):

Inject the SQSClient (infrastructure) into the SQSAdapter:

infraClient := aws_sqs.NewClient(cfg)
adapter := adapters_sqs.NewOrderEventsAdapter(infraClient, createOrderUseCase)
adapter.StartListening()

During runtime:

  • The adapter calls infraClient.ReceiveMessages(), receives technical messages.
  • Converts each sqs.Message into a domain event and calls the use case.
  • On success, calls infraClient.DeleteMessage() to remove it from the queue.

Benefits of this decoupling:

  • Testability: in domain tests you only need to mock ICreateOrderUseCase and use a fake ReceiveMessages().
  • Maintainability: changes in how you communicate with AWS (SDK, region, retry) are confined to the infrastructure package.
  • Clarity of responsibilities: each layer has a single focus. Adapters handle translating messages into business commands; infrastructure deals with network details, authentication and retries.

Question 3: Is there any problem having multiple folders named interfaces? I saw you put them in two different places, services/ and another in application/.

Answer:

It is not an error to have two directories called interfaces, as long as each represents a distinct abstraction level. See:

1. internal/domain/services/interfaces/

Role: abstract pure domain business rules, without knowledge of specific use cases.

Example contents:

  • IOrderServiceDomain — groups generic operations over orders (create, cancel, recalculate value).

Focus: entities and domain services (business modeling).

2. internal/application/interfaces/

Role: define contracts for use cases (use cases) that orchestrate business flows.

Example contents:

  • ICreateOrderUseCase — exposes only the method Execute(ctx, CreateOrderRequest) (CreateOrderResponse, error).
  • IListOrdersUseCase — exposes only Execute(ctx, ListOrdersRequest) ([]OrderDTO, error).

Focus: input/output of application flows, that is, orchestration of multiple domain services, validations and transactions.

Why keep them separate?

Single Responsibility (SRP):

  • Domain interfaces should not expose transport details (DTOs, HTTP context) or orchestration of multiple services.
  • Application interfaces do not need to know internal domain rules (value objects, entities).

Decoupling and clarity:

  • Makes it easier to understand “who does what” in each layer.
  • Easier to evolve the domain without touching use-case contracts and vice versa.

Testability

Conceptually, having two interfaces directories is expected in a Clean Architecture because they serve different levels of abstraction.


Question 4: Where are the ports (input/output) in this structure?

Answer:

In the layout you showed, the ports are already basically where they should be. Summarizing:

  • Input ports (use case boundaries)src/internal/application/interfaces/usecases/ Examples:

    • i_create_order_usecase.go
    • i_list_orders_usecase.go
  • Output ports (ports / repository contracts / external adapters contracts)src/internal/application/interfaces/ports/ Example:

    • i_order_repository.go
  • Domain interfaces / domain services (separate)src/internal/domain/services/interfaces/ Example:

    • i_order_service_domain.go

These three locations cover the main types of ports:

  • application/interfaces/usecases/ = input ports (who calls the system — the use cases).
  • application/interfaces/ports/ = output ports (what the system expects from external infra).
  • domain/services/interfaces/ = contracts for domain services (when there are services that encapsulate logic across entities).

How the other items connect to these ports

  • Adapters (input/output implementations) live under src/internal/adapters/:

    • HTTP, SQS, Lambda, gRPC adapters call the input ports (use cases).
    • Persistence adapters (e.g., adapters/persistence/order_repository.go) implement the output ports defined in application/interfaces/ports/.
  • Infrastructure (src/internal/infrastructure/) contains SDK clients and wrappers (e.g., aws/sqs/client.go, mongodb/client.go) that are used by implementations in adapters/persistence or by concrete adapters. Infrastructure provides primitives; adapters translate between those primitives and the ports.

  • Startup (cmd/*) wires dependencies: it creates infrastructure clients, instantiates concrete repositories (which implement the output ports), instantiates use cases (which receive the output ports), and finally plugs adapters that call the input ports.


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