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 wire to 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-runner tool (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-runner job, 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.