General Developer Guide
These conventions define how we build and maintain code across projects. Not all points apply to every project, but they set the general expectations.
Architecture
- Only immutable constants and pure helper functions are allowed as globals. All other code must be wrapped in objects.
- Use constructor injection and
wireto create and connect units at application startup. - Organize project packages by concern. Each concern should contain its handler, service, and repository if needed.
- Handlers should be lean. They convert requests into Go structures, perform basic input validation, and delegate to services or other dependencies.
- Repositories should be lean. If a database exists, all persistent data must be stored there, schema changes must go through versioned migrations, and implementations should consist only of simple queries without business logic.
- Clients: contain all code related to external systems, such as HTTP clients.
- Services: contain all business logic and orchestrate between handlers, repositories, and clients.
- All persistent data must be stored in the database.
Code Quality
- Code must be:
- readable (easy to understand),
- maintainable (easy to change),
- testable (easy to write tests for).
- Functions should be small, do one thing, and have descriptive names.
- Avoid duplication, deep nesting, and unnecessary comments.
- Tests must be written for all production code before merge.
- Errors and logging must follow the deepstack library conventions.
- All code must pass the CI pipeline, which may enforce formatting, style, and static analysis checks.
Technology Stack
- We use Go as our sole programming language.
- Keep the technology stack lean; only introduce new technologies when there is a clear and compelling need.
- If present, implement DevOps-related code in the
ci-runnertool (written in Go) and avoid Bash scripts. - Be conservative with dependencies; only add libraries that are popular, well-maintained, and permissively licensed.
Testing Strategy
- Tests must cover every feature before merging.
- Tests are included in the “test all”
ci-runnerjob, executed frequently in GitHub Actions. - Focus on both:
- happy paths: production code works as expected.
- unhappy paths: errors or edge cases are handled robustly (e.g., “user already exists”, “app does not exist”).
- Apply the appropriate test type(s):
- Unit tests: test individual backend units in isolation using mocks.
- Component tests: run the full application in Docker and test features exposed through the REST API via HTTP requests.
- Acceptance tests: use Cypress to verify frontend functionality.