. # 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 dependenciesDisclaimer: 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.
- .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.
- 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.
The cmd/ convention groups the application entry points. Each subfolder represents a distinct executable:
cmd/
├── api/
│ └── main.go
├── worker/
│ └── main.go
├── cli/
│ └── main.goIn 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/?
cmd/
- Contains Go executables that are part of the main application.
- Each folder in
cmd/(for exampleapi/,worker/,cli/) has amain.gothat 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?
To make it clearer, let’s think in terms of responsibilities, dependencies and testing:
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)orNotificationSender.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.
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.
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.Messageinto 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
ICreateOrderUseCaseand use a fakeReceiveMessages(). - Maintainability: changes in how you communicate with AWS (SDK, region, retry) are confined to the
infrastructurepackage. - 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/.
It is not an error to have two directories called interfaces, as long as each represents a distinct abstraction level. See:
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).
Role: define contracts for use cases (use cases) that orchestrate business flows.
Example contents:
ICreateOrderUseCase— exposes only the methodExecute(ctx, CreateOrderRequest) (CreateOrderResponse, error).IListOrdersUseCase— exposes onlyExecute(ctx, ListOrdersRequest) ([]OrderDTO, error).
Focus: input/output of application flows, that is, orchestration of multiple domain services, validations and transactions.
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.
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.goi_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).
-
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 inapplication/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 inadapters/persistenceor 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.