bxcodec/go-clean-arch
Go (Golang) Clean Architecture based on Reading Uncle Bob's Clean Architecture
Stale — last commit 2y ago
weakest axislast commit was 2y ago; no tests detected
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓10 active contributors
- ✓MIT licensed
- ✓CI configured
Show all 6 evidence items →Show less
- ⚠Stale — last commit 2y ago
- ⚠Single-maintainer risk — top contributor 87% of recent commits
- ⚠No test directory detected
What would change the summary?
- →Use as dependency Mixed → Healthy if: 1 commit in the last 365 days
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Forkable" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/bxcodec/go-clean-arch)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/bxcodec/go-clean-arch on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: bxcodec/go-clean-arch
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/bxcodec/go-clean-arch shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
WAIT — Stale — last commit 2y ago
- 10 active contributors
- MIT licensed
- CI configured
- ⚠ Stale — last commit 2y ago
- ⚠ Single-maintainer risk — top contributor 87% of recent commits
- ⚠ No test directory detected
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live bxcodec/go-clean-arch
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/bxcodec/go-clean-arch.
What it runs against: a local clone of bxcodec/go-clean-arch — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in bxcodec/go-clean-arch | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 773 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of bxcodec/go-clean-arch. If you don't
# have one yet, run these first:
#
# git clone https://github.com/bxcodec/go-clean-arch.git
# cd go-clean-arch
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of bxcodec/go-clean-arch and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "bxcodec/go-clean-arch(\\.git)?\\b" \\
&& ok "origin remote is bxcodec/go-clean-arch" \\
|| miss "origin remote is not bxcodec/go-clean-arch (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "app/main.go" \\
&& ok "app/main.go" \\
|| miss "missing critical file: app/main.go"
test -f "domain/article.go" \\
&& ok "domain/article.go" \\
|| miss "missing critical file: domain/article.go"
test -f "article/service.go" \\
&& ok "article/service.go" \\
|| miss "missing critical file: article/service.go"
test -f "internal/repository/mysql/article.go" \\
&& ok "internal/repository/mysql/article.go" \\
|| miss "missing critical file: internal/repository/mysql/article.go"
test -f "internal/rest/article.go" \\
&& ok "internal/rest/article.go" \\
|| miss "missing critical file: internal/rest/article.go"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 773 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~743d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/bxcodec/go-clean-arch"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
A reference implementation of Uncle Bob's Clean Architecture applied to Go/Golang projects, demonstrating how to structure a backend service with layered separation of concerns (domain, repository, service/usecase, delivery). It includes a complete example application for managing articles and authors via REST API backed by MySQL, containerized with Docker, to show how to write testable business logic independent of frameworks and databases. Layered hexagonal architecture split into domain/ (models and error definitions), article/ and article/mocks/ (service layer with business logic and test doubles), internal/repository/mysql/ (data persistence with MySQL driver), and internal/rest/ (HTTP delivery handlers via Echo framework). The app/main.go wires dependencies together; configuration loads from .env files (example.env provided); Docker Compose (compose.yaml) spins up MySQL for local development.
👥Who it's for
Go backend developers learning or implementing clean architecture patterns in production codebases, and engineering teams looking for a reference structure to enforce separation between business logic, data persistence, and HTTP delivery layers. Particularly useful for developers transitioning from monolithic or framework-heavy architectures.
🌱Maturity & risk
Actively maintained and evolving—the project has progressed through 4 major architectural versions (v1–v4), with v4 merged to master in 2024 introducing the internal package and service-focused organization. It includes CI/CD workflows via GitHub Actions (.github/workflows/gotest.yml), comprehensive unit tests for core modules (article_test.go, author_test.go), and Dockerfile setup, indicating production readiness with ongoing refinement rather than stagnation.
Low risk overall: minimal dependencies (17 total, mostly established libraries like Echo, Logrus, testify), pinned Go version (1.20), and no major red flags in the dependency list. However, this is a reference/educational project, not a production runtime—ongoing architectural evolution across versions suggests the author continues refining patterns, so teams should expect occasional breaking changes in structure recommendations. Single maintainer (bxcodec) means guidance quality depends on their continued engagement.
Active areas of work
The project is in a stable reference state post-v4 (2024 merge of PR #88). Active work is visible in GitHub Actions CI pipeline (gotest.yml), Dependabot integration for dependency updates, and Makefile tooling (misc/make/ with help and tools definitions). No specific open PRs or milestones are visible in provided data, suggesting the codebase is maintained but not under heavy active development—it serves as a living reference rather than a production service.
🚀Get running
Clone the repo, install Go 1.20+, copy example.env to .env, then run: make docker-up to start MySQL, and make test to verify the environment. For local development: make run or use .air.toml for hot-reloading with the Air tool (included in tools.Makefile).
Daily commands:
Local dev with hot reload: make run (requires Air tool from .air.toml). Full containerized stack: make docker-up (via compose.yaml with MySQL service). Tests: make test or go test ./.... Lint: make lint (uses .golangci.yaml). View all targets: make help (defined in misc/make/help.Makefile).
🗺️Map of the codebase
app/main.go— Application entry point that initializes the HTTP server, database connections, and wires all dependencies together.domain/article.go— Core domain entity defining the business logic boundary; all layers depend on this contract.article/service.go— Service layer implementing business rules and orchestrating repository access; the primary use-case handler.internal/repository/mysql/article.go— Data access layer implementation showing how domain entities persist to MySQL and how the repository pattern isolates database logic.internal/rest/article.go— HTTP handler layer that translates REST requests into service calls and demonstrates clean architecture's outer boundary.go.mod— Dependency manifest using Go 1.20; critical for understanding the framework choices (Echo, sqlmock, logrus).
🛠️How to make changes
Add a new API endpoint (e.g., GET /articles/:id)
- Define the handler function in internal/rest/article.go that accepts id parameter, calls service.FetchByID(), and returns JSON. (
internal/rest/article.go) - Register the route in app/main.go using echo.GET("/articles/:id", handler). (
app/main.go) - Add or extend the ArticleRepository interface in a new domain/repository.go to include GetByID() if needed. (
domain/article.go) - Implement GetByID() in internal/repository/mysql/article.go with the SQL query. (
internal/repository/mysql/article.go) - Add FetchByID() method to article/service.go that calls the repository and applies business logic. (
article/service.go) - Write tests in internal/rest/article_test.go with mocked service to verify the handler. (
internal/rest/article_test.go)
Add a new domain entity and repository
- Create domain/comment.go defining the Comment entity struct and any domain validation methods. (
domain/article.go) - Define CommentRepository interface in domain (via a new domain/repository.go or inline in comment.go). (
domain/article.go) - Create internal/repository/mysql/comment.go implementing all repository methods (Fetch, Store, Delete). (
internal/repository/mysql/article.go) - Create comment/service.go with use-case logic (e.g., CreateComment, GetComments) accepting repository interface. (
article/service.go) - Add HTTP handlers in internal/rest/comment.go and wire routes in app/main.go. (
internal/rest/article.go)
Migrate from MySQL to PostgreSQL
- Create internal/repository/postgres/ directory mirroring mysql/ structure with PostgreSQL driver (github.com/lib/pq). (
internal/repository/mysql/article.go) - Reimplement ArticleRepository in internal/repository/postgres/article.go with PostgreSQL syntax (parameterized queries). (
internal/repository/mysql/article.go) - Update app/main.go to instantiate PostgreSQL connection pool and inject postgres implementation instead of mysql. (
app/main.go) - Update docker-compose.yaml to spin up PostgreSQL service instead of MySQL. (
compose.yaml) - Migrate article.sql schema from MySQL to PostgreSQL dialect. (
article.sql)
Add middleware for request logging and tracing
- Create internal/rest/middleware/logger.go with a function that wraps Echo handlers and logs request/response metadata. (
internal/rest/middleware/cors.go) - Inject logrus logger from app/main.go into the middleware. (
app/main.go) - Register middleware in app/main.go using echo.Use() before route definitions. (
app/main.go) - Write tests in internal/rest/middleware/logger_test.go verifying log output format. (
internal/rest/middleware/cors_test.go)
🔧Why these technologies
- Echo (labstack/echo) — Lightweight, fast HTTP framework with built-in middleware support and minimal overhead—ideal for clean architecture pattern.
- MySQL (github.com/go-sql-driver/mysql) — Relational database providing ACID compliance; repository abstraction isolates schema changes from domain.
- Logrus (sirupsen/logrus) — Structured logging library enabling JSON output and context propagation across service boundaries.
- Testify (stretchr/testify) — Assertion library and mocking utilities simplifying unit test assertions and mock setup.
- sqlmock (DATA-DOG/go-sqlmock) — Enables testing repository layer without real database; mocks SQL queries and responses.
- Go 1.20+ — Modern Go features (context handling, generics prep, improved error handling) support clean architecture patterns naturally.
⚖️Trade-offs already made
-
Interfaces declared at consuming side (service layer) rather than provider side (repository layer).
- Why: Follows Dependency Inversion Principle; services own contracts they depend on, not repositories.
- Consequence: Looser coupling and easier testing, but requires careful interface design upfront to avoid interface pollution.
-
Repository pattern for data access rather than ORM (e.g., GORM).
- Why: Explicit control over queries and full database isolation via interfaces; cleaner domain logic.
- Consequence: More boilerplate SQL code; less rapid prototyping but better long-term maintainability and testability.
-
No centralized dependency injection container (manual DI in main.go).
- Why: Explicit, transparent wiring; easier to trace dependencies and understand initialization order.
- Consequence: app/main.go becomes verbose as codebase grows; consider wire or fx for larger projects.
-
Domain errors (domain/errors.go) as simple sentinel values rather than custom exception types.
- Why: Go idiom; lighter weight and easier to handle in conditionals.
- Consequence: Less context in errors; may need to extend with wrapped errors (fmt.Errorf %w) for debugging.
🚫Non-goals (don't propose these)
- Does not provide authentication or authorization—endpoints are fully public.
- Does not include real-time features (Web
🪤Traps & gotchas
MySQL connectivity required: compose.yaml defines a mysql service on port 3306; local development must either run make docker-up or have MySQL running separately. Environment variables: .env file must be present in project root (copy from example.env); missing it will cause DB connection failures. Database schema: article.sql must be applied to MySQL manually or via startup script—no embedded migrations in the code, so new developers may miss this step. Interface declaration location (v4 principle): interfaces should be declared in the consuming package, not the implementation package (e.g., ArticleRepository interface lives in article/ not internal/repository/), which is non-intuitive and easy to violate.
🏗️Architecture
💡Concepts to learn
- Hexagonal Architecture (Ports & Adapters) — The core pattern this repo teaches—isolating business logic from external systems (UI, database, frameworks) through well-defined boundaries, enabling the swappability of MySQL for PostgreSQL without touching service layer code
- Dependency Inversion Principle — Central to why this repo's layers work: high-level modules (service) depend on abstractions (interfaces), not low-level details (MySQL driver); enables testability via mock implementations in article/mocks/
- Repository Pattern — Abstracts data persistence (internal/repository/mysql/) behind a repository interface, allowing business logic (article/service.go) to be tested with mock data sources without touching real databases
- Middleware Chain (in HTTP delivery) — Echo middleware (CORS, timeout handlers in internal/rest/middleware/) wraps HTTP request/response handling orthogonally to business logic, showing how cross-cutting concerns stay out of domain layer
- Interface Segregation & Consuming-Side Interface Declaration — V4 introduces declaring interfaces in consuming packages (e.g., ArticleRepository in article/, not internal/repository/)—prevents dependency bloat and makes contracts explicit where they're used
- Test Doubles (Mocks & Stubs) — article/mocks/ and internal/rest/mocks/ provide mock implementations of interfaces, allowing service tests to run without MySQL, demonstrating why clean boundaries enable testability without external dependencies
- Golang internal Package Convention — Code in internal/ is not importable by external packages, enforcing architectural boundaries; this repo uses it to prevent cross-layer imports and signal that repository/rest are implementation details, not public APIs
🔗Related repos
golang-standards/project-layout— De facto Go project structure standard; complementary reference for directory organization that aligns with theinternal/package strategy shown heredocker/awesome-compose— Provides additional Docker Compose examples for common service stacks (like the MySQL setup in compose.yaml); useful for expanding the local dev environmentuber-go/fx— Dependency injection framework for Go that automates the wiring done manually inapp/main.go; a potential evolution path for larger projects following clean architectureurfave/cli— CLI framework complementing this REST/HTTP-focused example; shows how clean architecture applies to command-line applications as an alternative delivery layergo-kit/kit— Microservices toolkit that enforces layered architecture (endpoint → service → middleware) similar to the clean architecture pattern, for teams scaling beyond a monolith
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add integration tests for internal/rest/article_test.go covering Echo middleware chain
The internal/rest/article_test.go exists but likely lacks comprehensive tests for the CORS and timeout middleware integration (internal/rest/middleware/cors.go and timeout.go). Currently only cors_test.go has isolated tests. New contributor should add end-to-end tests validating the middleware chain works correctly with actual HTTP handlers, testing scenarios like timeout enforcement and CORS header validation in article endpoints.
- [ ] Review internal/rest/article_test.go to identify missing middleware integration test cases
- [ ] Add test cases in internal/rest/article_test.go that instantiate Echo with CORS and timeout middleware configured
- [ ] Test timeout middleware behavior with slow repository calls using context cancellation
- [ ] Test CORS header propagation through the full request/response cycle
- [ ] Validate error handling when timeouts occur (compare with internal/rest/middleware/timeout.go implementation)
Add context timeout propagation tests in internal/repository/mysql/article_test.go and author_test.go
The repository layer (internal/repository/mysql/) has test files but they likely use sqlmock in isolation. With the timeout middleware in place (internal/rest/middleware/timeout.go uses context.WithTimeout), the repository methods should be tested to ensure they respect context deadlines. New contributor should add tests validating that database queries are cancelled when context expires, preventing resource leaks.
- [ ] Review internal/repository/mysql/article_test.go and author_test.go for existing context deadline handling tests
- [ ] Add test cases that create contexts with short timeouts and verify queries are cancelled
- [ ] Use sqlmock to simulate slow database operations and confirm context cancellation is respected
- [ ] Test both successful query completion and timeout scenarios for GetByID, Fetch, Store, and Delete methods
- [ ] Document the pattern in internal/README.md for other repository implementations
Complete v4 changelog in README.md and add migration guide from v3
The README.md shows that v4 was 'Proposed on 2024, merged to master on 2024' but the description is incomplete ('Desc:' with only a bullet point about declaring interfaces). The incomplete changelog and missing migration documentation from v3 to v4 creates friction for users. New contributor should complete the v4 description and add a migration guide explaining what changed architecturally.
- [ ] Review git history and PRs merged into master (particularly #21 mentioned for v3) to understand v4 improvements
- [ ] Complete the v4 description in README.md changelog section with details on interface declaration changes and architectural improvements
- [ ] Add a new 'Migration from v3 to v4' section explaining breaking changes and how to update existing v3 projects
- [ ] Document where interfaces are now declared (consuming side pattern) with code examples from article/service.go and domain/
- [ ] Link to relevant PR(s) that introduced v4 changes, similar to how v1-v3 link to Medium posts
🌿Good first issues
- Add unit test coverage for
internal/rest/article.go(mirror the pattern fromarticle/service_test.go); currently rest handlers lack explicit route/response tests despite service layer being well-tested, leaving a coverage gap in the delivery layer. - Write integration tests in a new
tests/integration/directory using docker-compose to spin up real MySQL and verify the full request → service → repository → database flow works (currently only unit tests with mocks exist). - Document the v4 architectural decision (interfaces declared in consuming side, internal package structure) with a diagram and example in
internal/README.md; the README mentions it but beginners struggle to understand when/where to apply it in practice.
⭐Top contributors
Click to expand
Top contributors
- @bxcodec — 61 commits
- @dependabot[bot] — 1 commits
- @IvanReyesO7 — 1 commits
- @Bogdaan — 1 commits
- @h4yfans — 1 commits
📝Recent commits
Click to expand
Recent commits
e06c6d0— chore(deps): bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#94) (dependabot[bot])7347d9a— chore: add dependabot (#93) (bxcodec)7699187— fix: Update Variable Name for Server Address in main.go (#92) (IvanReyesO7)beb599f— feat: introduce v4 (#88) (bxcodec)d5d2bac— chore: fix typo (bxcodec)4f7f659— chore: fix typo readme (bxcodec)7b8cd9b— chore: linting and cleanup (#70) (bxcodec)4cbd13b— chore: add sponsor button (bxcodec)2d4f0da— chore: fix error style https://github.com/golang/go/wiki/CodeReviewComments#error-strings (#49) (Bogdaan)9e174b8— docs: remove unused information in Readme (bxcodec)
🔒Security observations
- High · Outdated Go Version in Dockerfile —
Dockerfile (line 2: FROM golang:1.20.7-alpine3.17). The Dockerfile uses Go 1.20.7 which is significantly outdated. Go 1.20 reached end-of-life in August 2024. This exposes the application to known security vulnerabilities in the Go runtime and standard library that have been patched in newer versions. Fix: Update to Go 1.22 or later (current stable versions: 1.23+). Modify the builder stage to use a recent, supported Go version. - High · Outdated Alpine Base Image —
Dockerfile (line 12: FROM alpine:latest). The distribution stage uses 'alpine:latest' which is unpinned and may resolve to an outdated version. Additionally, Alpine 3.17 in the builder is aging. Unpinned base images can lead to inconsistent builds and potential security gaps. Fix: Pin to a specific Alpine version (e.g., 'alpine:3.20' or later). Use a specific version tag instead of 'latest' for reproducibility and security. - High · Deprecated Validator Package —
go.mod (validator.v9 v9.31.0). The codebase uses 'gopkg.in/go-playground/validator.v9 v9.31.0' which is deprecated. The project has moved to 'github.com/go-playground/validator' (v10+). Using deprecated packages means missing security patches and bug fixes. Fix: Migrate to 'github.com/go-playground/validator/v10'. Update all validation code to use the new import path and API. - High · Deprecated SQL Mock Package —
go.mod (go-sqlmock.v1 v1.3.0). The codebase uses 'gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0' which is outdated. The maintained version is 'github.com/DATA-DOG/go-sqlmock' (v2+). Deprecated test packages may contain unpatched security issues. Fix: Upgrade to 'github.com/DATA-DOG/go-sqlmock' v2 or later. Update test code to use the new import path. - Medium · Potential SQL Injection in Repository Layer —
internal/repository/mysql/article.go, internal/repository/mysql/author.go. The file structure indicates raw SQL query handling in 'internal/repository/mysql/' without visible parameterization examples. If SQL queries are constructed with string concatenation rather than prepared statements, SQL injection attacks are possible. Fix: Ensure all database queries use parameterized statements or prepared statements. Avoid string concatenation for building SQL queries. Implement input validation and use ORM or query builders if possible. - Medium · Missing CORS Security Configuration —
internal/rest/middleware/cors.go. The middleware includes a CORS handler ('internal/rest/middleware/cors.go'). If not properly configured, it could allow unintended cross-origin requests. Default CORS configurations often allow all origins (''). Fix: Configure CORS with explicit whitelisted origins. Avoid using wildcard '' for AllowOrigins in production. Set appropriate headers (AllowCredentials, AllowHeaders, AllowMethods). Review the middleware configuration. - Medium · Exposed Application Port Without Authentication —
Dockerfile (EXPOSE 9090), likely internal/rest/article.go. The Dockerfile exposes port 9090. If the application serves the REST API without proper authentication/authorization on all endpoints, unauthenticated users could access sensitive endpoints. Fix: Implement authentication (JWT, OAuth2, etc.) on all sensitive endpoints. Validate all incoming requests. Implement authorization checks based on user roles/permissions. Use HTTPS in production. - Medium · Insecure Timeout Configuration —
internal/rest/middleware/timeout.go. The middleware includes a timeout handler ('internal/rest/middleware/timeout.go'). If misconfigured (too long or missing), it could allow Slowloris or other denial-of-service attacks. Fix: Implement appropriate request timeouts (
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.