{"id":37158020,"url":"https://github.com/sklinkert/go-ddd","last_synced_at":"2026-01-14T18:51:55.251Z","repository":{"id":207883039,"uuid":"677511480","full_name":"sklinkert/go-ddd","owner":"sklinkert","description":"Opinionated Domain Driven Design Template for Go","archived":false,"fork":false,"pushed_at":"2026-01-01T12:11:59.000Z","size":11465,"stargazers_count":491,"open_issues_count":7,"forks_count":50,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-06T10:49:19.015Z","etag":null,"topics":["architecture","command-query","cqrs","cqrs-pattern","ddd","ddd-architecture","domain","domain-driven-design","go","golang","gorm-orm","idempotency","reference","rest-api","template","web"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sklinkert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-08-11T19:04:31.000Z","updated_at":"2026-01-05T20:37:08.000Z","dependencies_parsed_at":"2023-11-18T07:54:45.125Z","dependency_job_id":"62f6edcc-ae8a-4210-b299-e614829441ae","html_url":"https://github.com/sklinkert/go-ddd","commit_stats":null,"previous_names":["sklinkert/go-ddd"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/sklinkert/go-ddd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sklinkert%2Fgo-ddd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sklinkert%2Fgo-ddd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sklinkert%2Fgo-ddd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sklinkert%2Fgo-ddd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sklinkert","download_url":"https://codeload.github.com/sklinkert/go-ddd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sklinkert%2Fgo-ddd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28430942,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T16:38:47.836Z","status":"ssl_error","status_checked_at":"2026-01-14T16:34:59.695Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","command-query","cqrs","cqrs-pattern","ddd","ddd-architecture","domain","domain-driven-design","go","golang","gorm-orm","idempotency","reference","rest-api","template","web"],"created_at":"2026-01-14T18:51:54.591Z","updated_at":"2026-01-14T18:51:55.244Z","avatar_url":"https://github.com/sklinkert.png","language":"Go","readme":"# Go-DDD: Build Domain-Driven Go Services Fast\n\n`go-ddd` jump-starts production-grade Go backends that keep business rules, infrastructure, and delivery code cleanly separated. Out of the box you get opinionated DDD building blocks, CQRS command and query flows, idempotent write paths, and tooling to keep schema and code in lockstep.\n\n## Why This Template\n\n- **Model-first defaults** – Onion architecture keeps the domain pure while application services orchestrate infrastructure concerns.\n- **Battle-tested patterns** – Commands, queries, repositories, and soft deletes mirror patterns used in real-world enterprise applications.\n- **Idempotent pipelines** – Retry-safe command handlers prevent duplicate writes and highlight resilient workflows.\n- **Migration discipline** – SQL migrations, `migrate.go`, and `sqlc` make schema evolution explicit and reproducible.\n\n## What You Get\n\n- Marketplace example that demonstrates aggregates (Seller, Product), cross-module interactions, and validation rules.\n- Layered modules under `internal/` for `domain`, `application`, `infrastructure`, `interface`, plus `testhelpers` for fixture reuse.\n- Executable entrypoint at `cmd/marketplace/main.go` ready to wire adapters or frameworks of your choice.\n- Database assets in `migrations/` and `sql/` plus generated data access via `sqlc`.\n\n## Tech Stack Essentials\n\n- **Go 1.24** with idiomatic patterns and testify-powered tests.\n- **Echo v4** HTTP stack for REST endpoints.\n- **pgx/v5** and `sqlc` for type-safe PostgreSQL access.\n- **golang-migrate** handling SQL schema migrations\n- **Testcontainers** integration to provision disposable Postgres instances during tests.\n- **google/uuid** helpers for deterministic ID generation inside the domain.\n\n## Design Principles in Action\n\nDomain-Driven Design connects implementation to an evolving model. `go-ddd` showcases this by modelling a simple marketplace where `Sellers` manage `Products`, exercising aggregates, value objects, and validation flows.\n\n## Documentation\n\n📚 **[Comprehensive DDD \u0026 CQRS Principles Guide](DDD_CQRS_PRINCIPLES.md)** - Learn how to apply these patterns to any business domain.\n\n## Repository Structure\n\n![ddd-diagram-onion.png](ddd-diagram-onion.png)\n\n- `domain`: The heart of the software, representing business logic and rules.\n    - `entities`: Fundamental objects within our system, like `Product` and `Seller`. Contains basic validation logic.\n- `application`: Contains use-case specific operations that interact with the domain layer.\n- `infrastructure`: Supports the higher layers with technical capabilities like database access.\n    - `db`: Database access and models.\n    - `repositories`: Concrete implementations of our storage needs.\n- `interface`: The external layer which interacts with the outside world, like API endpoints.\n    - `api/rest`: Handlers or controllers for managing HTTP requests and responses.\n\n## Further principles\n\n- Domain\n  - Must not depend on other layers.\n  - Provides infrastructure with interfaces, but must not access infrastructure.\n  - Implements business logic and rules.\n  - Executes validations on entities. Validated entities are passed to the infrastructure layer.\n  - Domain layer sets defaults of entities (e.g. uuid for ID or creation timestamp). Don't set defaults in the infrastructure layer or even database!\n  - Do not leak domain objects to the outside world.\n- Application\n  - The glue code between the domain and infrastructure layer.\n- Infrastructure\n   - Repositories are responsible for translating a domain entity to a database model and retrieving it. No business logic is executed here.\n   - Implements interfaces defined by the domain layer.\n   - Implements persistence logic like accessing a postgres or mysql database.\n   - When writing to storage, read written data before returning it. This ensures that the data is written correctly.\n\n## Best Practices\n\n- Don't return validated entities from read methods in the repository. Instead, return the domain entity type directly.\n  - Validations will change over time. You don't want to migrate all the data in your database. Instead, you should guarantee you can always load historical data, regardless of how your validation logic has evolved.\n  - Otherwise, you won't be able to read data from the database that was written with a different validation logic. You will have to handle errors at runtime.\n  - Push validation to the write side-creation (NewX) and update methods - where you must enforce invariants anyway.\n- Don't put default values (e.g current timestamp or ID) in the database. Set them in the domain layer (factory!) for several reasons:\n  - It's quite dangerous to have two sources of truth.\n  - It's easier to test the domain layer.\n  - Databases can get replaced, and you don't want to have to change all your default values. \n- Always read the entity after write in the infrastructure layer.\n  - This ensures that the data is written correctly, and we are never operating on stale data.\n- `find` vs `get`:\n  - `find` methods can return null or an empty list.\n  - `get` methods must return a value. If the value is not found, throw an error.\n- Deletion: Always use soft deletion. Create a `deleted_at` column in your database and set it to the current timestamp when deleting an entity. This way, you can always restore the entity if needed.\n\n## CQRS and Idempotency\n\n### Command Query Responsibility Segregation (CQRS)\nCQRS separates read operations (queries) from write operations (commands) in your application. In this codebase:\n- **Commands** modify state (CreateSellerCommand, CreateProductCommand, UpdateSellerCommand)\n- **Queries** retrieve data without side effects (FindAllSellers, FindSellerById)\n\nThis separation enables different optimization strategies:\n- **Write optimization**: Commands can use normalized schemas, ACID transactions, and strong consistency\n- **Read optimization**: Queries can use denormalized views, caching, read replicas, or even different databases (e.g., PostgreSQL for writes, Elasticsearch for reads)\n- **Scalability**: Read and write workloads can be scaled independently based on actual usage patterns\n- **Performance**: Complex queries don't impact write performance, and write locks don't block read operations\n\n### Idempotency Keys\nIdempotency ensures that multiple identical requests have the same effect as a single request. This is crucial for handling network failures and retries in distributed systems. Implementation:\n- Each command accepts an optional `idempotency_key` in the request\n- The application layer checks if this key has been processed before\n- If yes, it returns the cached response without re-executing business logic\n- If no, it executes the command and stores the response for future requests\n\nThis prevents duplicate entities from being created when clients retry failed requests.\n\n## Database Migrations\n\nThis project uses [golang-migrate](https://github.com/golang-migrate/migrate) for database schema management. Migrations are stored in the `migrations/` directory with sequential version numbers.\n\n### Migration Files Structure\n```\nmigrations/\n├── 000001_initial_schema.up.sql    # Creates initial tables\n└── 000001_initial_schema.down.sql  # Rollback for initial schema\n```\n\n### Running Migrations\n\n#### Using the built-in utility:\n```bash\n# Apply all pending migrations\ngo run migrate.go -database-url \"postgres://user:pass@localhost/db?sslmode=disable\" -command up\n\n# Rollback last migration\ngo run migrate.go -database-url \"postgres://user:pass@localhost/db?sslmode=disable\" -command down -steps 1\n\n# Check current version\ngo run migrate.go -database-url \"postgres://user:pass@localhost/db?sslmode=disable\" -command version\n\n# Force to specific version (use with caution)\ngo run migrate.go -database-url \"postgres://user:pass@localhost/db?sslmode=disable\" -command force -version 1\n```\n\n#### Using the CLI tool directly:\n```bash\n# Install the CLI tool\ngo install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest\n\n# Apply all pending migrations\nmigrate -path migrations -database \"postgres://user:pass@localhost/db?sslmode=disable\" up\n\n# Rollback last migration\nmigrate -path migrations -database \"postgres://user:pass@localhost/db?sslmode=disable\" down 1\n```\n\n### Creating New Migrations\n```bash\n# Create a new migration\nmigrate create -ext sql -dir migrations -seq add_user_email_column\n```\n\nThis will create two files:\n- `000002_add_user_email_column.up.sql` - Forward migration\n- `000002_add_user_email_column.down.sql` - Rollback migration\n\n### Migration Best Practices\n- Always create both `up` and `down` migrations\n- Test migrations on a copy of production data\n- Keep migrations small and focused\n- Never modify existing migration files once they've been applied in production\n- Use descriptive names for migration files\n\n## Getting Started\n\n1. Clone this repository:\n```bash\ngit clone https://github.com/sklinkert/go-ddd.git\ncd go-ddd\ngo mod download\n```\n\n2. Install sqlc (for development):\n```bash\ngo install github.com/sqlc-dev/sqlc/cmd/sqlc@latest\n```\n\n3. Generate database code (if you modify SQL queries):\n```bash\nsqlc generate\n```\n\n4. Set up your PostgreSQL database and run migrations:\n```bash\n# Set your database connection URL\nexport DATABASE_URL=\"postgres://user:password@localhost/dbname?sslmode=disable\"\n\n# Run migrations using the built-in utility\ngo run migrate.go -command up\n\n# Or use the CLI tool directly\nmigrate -path migrations -database $DATABASE_URL up\n```\n\n5. Run the application:\n```bash\ngo run ./cmd/marketplace\n```\n\n### Contributions\nContributions, issues, and feature requests are welcome! Feel free to check the issues page.\n\n### License\nDistributed under the MIT License. See LICENSE for more information.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsklinkert%2Fgo-ddd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsklinkert%2Fgo-ddd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsklinkert%2Fgo-ddd/lists"}