Skip to content

Principles and Guidelines

This document outlines the core principles, guidelines, and processes we follow in developing and designing our software. Adhering to these helps us build a maintainable, robust, and understandable system.

Guidelines, Rules and Processes

These are specific practices and rules we’ve agreed upon. While flexibility is sometimes needed, these should be followed by default.

[GP0] Trunk Based Development

We use trunk based development as our branching model. All developers commit to a single shared trunk (e.g., main or master). Feature development happens in short-lived branches that are merged frequently (ideally daily) into the trunk after code review and passing automated tests.

  • Reasoning: Simplifies branching logic compared to GitFlow, promotes continuous integration, encourages smaller, more frequent commits, and speeds up delivery.
  • Architectural Significance: Facilitates CI/CD by ensuring the main branch is always releasable or close to it. Reduces merge conflicts and integration complexity.

[GP1.0] (Automated) Tests

Automated tests are essential for verifying functionality, preventing regressions, and enabling safe refactoring. We rely on a suite of tests to ensure software quality at different levels.

  • Motivation: Increase confidence in code changes, provide fast feedback, serve as living documentation for component behavior. Industry best practice.
  • Architectural Significance: Ensures components and their interactions work as designed, supports modularity, crucial for reliable CI/CD pipelines and maintaining system stability.

[GP1.1] Unit Tests

Write unit tests to verify the correctness of individual components (classes, functions, modules) in isolation. Mock dependencies where necessary. Aim for good coverage of logic paths, especially for core business logic.

  • Motivation: Provide rapid feedback during development, pinpoint errors precisely, easy to write and fast to execute.
  • Architectural Significance: Validates the fundamental building blocks of the architecture. Encourages writing testable, loosely coupled code.

[GP1.2] End-to-End (E2E) Tests

Implement E2E tests that simulate real user scenarios, testing the entire system flow from the user interface (if applicable) through backend services to the database. Focus on critical user journeys and core functionalities.

  • Motivation: Verify that integrated components work together correctly to fulfill user needs. Catch issues that unit or integration tests might miss.
  • Architectural Significance: Validates the overall system behavior and ensures that architectural components are correctly orchestrated to deliver business value.

[GP1.3] Integration Tests

Use integration tests to verify the interactions and contracts between specific components or services, including interactions with external systems (databases, third-party APIs).

  • Motivation: Catch issues related to component communication, data format mismatches, or incorrect assumptions about dependencies.
  • Architectural Significance: Ensures that the defined interfaces and communication pathways within the architecture function correctly. Validates architectural boundaries.

[GP2] Code Style

We enforce a consistent code style using [TODO: decide on linter for front & backend]. The configuration is located in the repository [TODO: replace with correct paths, e.g., /frontend/.eslintrc.js, /backend/pyproject.toml] and enforced automatically via pre-commit hooks and CI checks.

  • Motivation: Improves code readability and maintainability, reduces cognitive load when switching between files/modules, avoids subjective style debates in code reviews.
  • Architectural Significance: Consistent code style makes the entire codebase easier to navigate and understand, supporting easier maintenance and evolution of the architecture.

[GP3] Code Reviews

All code changes intended for the main branch must undergo a code review by at least one other team member. Reviews should focus on correctness, design, maintainability, security, test coverage, and adherence to established guidelines and principles.

  • Motivation: Early bug detection, knowledge sharing across the team, upholding code quality and standards, mentoring opportunity.
  • Architectural Significance: Acts as a critical quality gate, ensures architectural patterns are followed consistently, and helps maintain the overall health and integrity of the codebase.

[GP4] Documentation

Maintain relevant and up-to-date documentation. Key areas include:

  • Architecture Overview: High-level design, component diagrams (using C4 model or similar).

  • Key Design Decisions: Use Architecture Decision Records (ADRs) to document significant choices and their rationale.

  • Setup & Deployment: Instructions for setting up a development environment and deploying the application.

  • APIs: Clear documentation for any APIs (internal or external), preferably using standards like OpenAPI/Swagger.

  • READMEs: Each service/module should have a README explaining its purpose and usage.

  • Motivation: Facilitates onboarding new team members, aids understanding of the system, supports troubleshooting and maintenance, preserves design rationale.

  • Architectural Significance: Makes the architecture explicit and understandable. Essential for long-term maintainability, scalability, and evolution of the system.

[GP5] Definition of Done (DoD)

A work item (e.g., user story, task, bug fix) is considered “Done” only when it meets the following criteria:

  • Code implemented according to requirements.

  • Relevant unit and integration tests written and passing.

  • E2E tests (if applicable) updated and passing.

  • Code adheres to style guidelines (linters pass).

  • Code successfully builds and passes all CI checks.

  • Code reviewed and approved by peers.

  • Necessary documentation (code comments, READMEs, ADRs, API docs) updated.

  • Functionality manually tested/verified (if applicable).

  • Merged to the main branch and deployed to TODO: Correct name for Integration Test Environment.

  • Motivation: Ensures consistency and quality for all delivered work, sets clear expectations, prevents misunderstandings about completeness.

  • Architectural Significance: Guarantees that changes integrated into the system meet agreed quality and architectural standards before impacting users or downstream systems.

[GP6] Technology Decisions

Decisions about adopting new major technologies (frameworks, libraries, databases, cloud services, etc.) should be made collaboratively. Justify choices based on:

  • Project requirements (functional and non-functional).

  • Team familiarity and expertise.

  • Long-term maintainability and operational cost.

  • Community support and ecosystem maturity.

  • Alignment with existing architecture and strategic goals. Document significant decisions using ADRs.

  • Motivation: Promotes informed choices, avoids “shiny object syndrome,” ensures alignment within the team, reduces technical debt risk.

  • Architectural Significance: These decisions fundamentally shape the architecture. Documenting the rationale provides crucial context for future evolution and prevents revisiting settled debates.

[GP7] Well Defined APIs

Design APIs (REST, GraphQL, gRPC, internal libraries) thoughtfully:

  • Clear Contracts: Use specifications like OpenAPI for REST APIs. Define clear schemas/protobufs.

  • Consumer-Focused: Design based on the needs of the API consumers.

  • Consistency: Follow established naming conventions and patterns (e.g., resource naming, status codes, error handling).

  • Discoverability: Make APIs easy to find and understand.

  • Motivation: Enables effective and reliable communication between system components, facilitates independent development and deployment (e.g., microservices), simplifies integration.

  • Architectural Significance: APIs define the boundaries and contracts between architectural components. Well-designed APIs are crucial for creating loosely coupled, scalable, and maintainable systems.

  • Resources:

[GP8] GitOps

[TODO Rewrite this section once deployment process has been defined] We manage our infrastructure provisioning and application deployment processes using GitOps principles. The desired state of our environments (e.g., Kubernetes manifests, Terraform configurations) is defined declaratively in a Git repository. An automated process (e.g., Argo CD, Flux) ensures the live environment converges to the state defined in Git.

  • Motivation: Provides a single source of truth, auditability (Git history), consistency across environments, automated and reliable deployments, improved security through declarative configuration.
  • Architectural Significance: Integrates infrastructure and deployment concerns directly into the version-controlled development workflow. Enhances operational stability and reproducibility of the system’s runtime architecture.

[GP9] Logging and Monitoring

Implement consistent logging across all services. Logs should be structured (e.g., JSON) and include relevant context (request IDs, user IDs if applicable). Establish key monitoring metrics (e.g., request latency, error rates, resource utilization) and dashboards to observe system health and performance.

  • Motivation: Essential for debugging issues, understanding system behavior, performance analysis, and security auditing.
  • Architectural Significance: Provides observability into the running system, crucial for operating and maintaining the architecture effectively. Helps identify bottlenecks and failures.

[GP10] Security Practices

Follow fundamental security best practices:

  • Validate and sanitize all external input.

  • Use parameterized queries or ORMs to prevent SQL injection.

  • Implement proper authentication and authorization checks.

  • Keep dependencies updated and scan for vulnerabilities (e.g., using Dependabot, Snyk).

  • Store secrets securely (e.g., using a secrets manager), not in code or configuration files.

  • Apply the principle of least privilege.

  • Motivation: Protects user data, prevents unauthorized access, maintains system integrity, complies with regulations.

  • Architectural Significance: Security must be designed into the architecture, not bolted on later. Affects component interaction, data storage, and infrastructure choices.

[GP11] Configuration Management

Externalize configuration from the application code. Use environment variables, configuration files, or a dedicated configuration service. Ensure configuration differs appropriately across environments (dev, staging, prod) without requiring code changes.

  • Motivation: Allows deploying the same application artifact to different environments, improves security (avoids hardcoding secrets), simplifies configuration updates.
  • Architectural Significance: Decouples the application logic from its runtime environment, improving deployability and operational flexibility.

Development Principles

In contrast to the specific guidelines above, the following principles represent our development philosophy. They guide our decisions but allow for exceptions when justified. Think of them as preferred ways of thinking about problems.

KISS

Keep it simple, stupid. Favor simple, straightforward solutions over complex ones whenever possible. Complexity increases the chances of bugs and makes maintenance harder. Ask: “Is there a simpler way to achieve this?”

Measure Twice and Cut Once

Before committing to significant implementation effort or architectural changes, take time to understand the problem, explore alternatives, and consider potential consequences. A little upfront thinking can save significant rework later.

  • Motivation: Avoids wasted effort on ill-conceived solutions, reduces technical debt, leads to better-designed systems.
  • Architectural Significance: Encourages thoughtful design and analysis, leading to more robust and appropriate architectural choices.

YAGNI / Don’t Reinvent the Wheel / Avoid Premature Optimization

  • YAGNI: You aren’t gonna need it. Implement only the functionality required now. Resist the urge to add features or flexibility “just in case” for hypothetical future needs.

  • Don’t Reinvent the Wheel: Leverage existing, well-tested libraries, frameworks, and services where they fit the need. Focus your effort on the unique aspects of the problem.

  • Avoid Premature Optimization: Don’t optimize code for performance unless profiling and measurement clearly indicate a bottleneck. Focus first on clarity, correctness, and simplicity. Optimization often increases complexity.

  • Motivation: Keeps the codebase lean, reduces development time, avoids unnecessary complexity, focuses effort on delivering current value.

  • Architectural Significance: Prevents over-engineering. Promotes simpler, more adaptable architectures that can evolve based on actual requirements, not speculation.

SOLID

Strive to apply the SOLID principles in object-oriented design (and often applicable elsewhere):

  • Single-responsibility principle: A class/module should have only one reason to change.

  • Open-closed principle: Software entities should be open for extension, but closed for modification.

  • Liskov substitution principle: Subtypes must be substitutable for their base types without altering correctness.

  • Interface segregation principle: Clients should not be forced to depend on interfaces they do not use.

  • Dependency inversion principle: Depend upon abstractions, not concretions.

  • Motivation: Leads to more understandable, flexible, maintainable, and testable code.

  • Architectural Significance: Promotes modularity, loose coupling, and resilience to change within the system’s components.

High Cohesion, Low Coupling. / Law of Demeter

  • High Cohesion: Elements within a module/component should be strongly related and focused on a single, well-defined purpose.

  • Low Coupling: Modules/components should be minimally dependent on each other. Changes in one module should have minimal impact on others.

  • Law of Demeter (Principle of Least Knowledge): Law of Demeter. An object should only talk to its immediate “friends” (its own methods, parameters, objects it creates, direct component objects) and not reach through them to talk to strangers.

  • Motivation: Improves modularity, makes the system easier to understand, test, and maintain. Reduces the ripple effect of changes.

  • Architectural Significance: These are fundamental principles for designing well-structured architectures, particularly important for microservices or any system composed of distinct parts. They enable independent development and evolution of components.

DRY (Don’t Repeat Yourself)

Don’t Repeat Yourself. Avoid duplication of code, logic, data, or configuration. Strive to have a single, authoritative representation of every piece of knowledge within the system. Use functions, classes, configuration, or other abstractions to achieve this.

  • Motivation: Reduces maintenance effort (change only in one place), prevents inconsistencies, improves clarity.
  • Architectural Significance: Promotes reusable components and consistent behavior across the system. Duplication often indicates missed opportunities for abstraction or shared services.

Boy Scout Rule

“Always leave the campground cleaner than you found it.” When working on a piece of code, take the opportunity to make small improvements: improve variable names, clarify comments, extract a function, add a missing test. Even small cleanups accumulate over time.

  • Motivation: Gradually improves code quality and maintainability over time, counteracts entropy.
  • Architectural Significance: Contributes to the long-term health and sustainability of the codebase and architecture by encouraging continuous, incremental refinement.