Skip to content

Engineering Philosophy

Good software is not measured by the amount of code it contains, but by how easy it is to understand, evolve and trust.

This document defines the engineering philosophy of StrataStax.

Every architectural decision, coding guideline and code review should ultimately align with these principles.

When individual guidelines appear to conflict, this document takes precedence.


We build software that is:

  • Easy to understand
  • Easy to change
  • Easy to test
  • Easy to reason about
  • Difficult to misuse
  • Explicit rather than magical

We optimize for long-term maintainability over short-term convenience.


Code is read far more often than it is written.

Prefer the simplest solution that correctly solves the problem.

Avoid unnecessary abstraction, indirection or premature optimization.

If a junior developer can understand the solution without explanation, that is usually a good sign.


Hidden behavior increases cognitive load.

Code should clearly communicate:

  • what it does
  • why it exists
  • where responsibilities lie

Avoid framework magic whenever practical.

Favor explicit code over convention-driven behavior.


Functional programming leads to software that is easier to reason about and easier to test.

Whenever practical:

  • prefer pure functions
  • minimize side effects
  • compose behaviour from small functions
  • keep data immutable

Object-oriented design is used where it naturally models business concepts and boundaries.


State is one of the largest sources of complexity.

Objects should therefore be immutable unless mutation provides a clear benefit.

Prefer:

  • immutable objects
  • immutable collections
  • value objects
  • pure transformations

Mutation should be deliberate and isolated.


Frameworks are implementation details.

The architecture defines the system.

The framework supports the architecture—not the other way around.

Business logic should not depend on web frameworks, persistence frameworks or infrastructure concerns.


Software should be organized around business capabilities.

A feature contains everything required to implement that capability.

Avoid project structures based solely on technical concerns such as:

controller/
service/
repository/
entity/

Instead prefer:

customer/
order/
invoice/

Each feature contains its own presentation, application, domain and infrastructure code.

This improves cohesion while reducing coupling.


Every layer has exactly one responsibility.

Presentation handles communication.

Application coordinates use cases.

Domain contains business rules.

Infrastructure interacts with external systems.

Responsibilities should never bleed across architectural boundaries.


Every architectural boundary should have a well-defined contract.

Dependencies always point inward.

External systems are accessed through adapters.

Business logic should remain independent from implementation details.


Engineering principles exist to improve software—not to win debates.

Use SOLID.

Use Clean Code.

Use Functional Programming.

But never apply patterns simply because they exist.

Every abstraction should solve an actual problem.


Tests exist to increase confidence.

Not every line of code deserves its own unit test.

Instead, invest testing effort where it provides the greatest value.

We prefer:

  • integration tests
  • end-to-end tests
  • focused unit tests for complex logic

Tests should verify behaviour, not implementation.


Premature optimization creates complexity.

Prefer readability first.

Measure.

Identify bottlenecks.

Optimize only when evidence justifies the additional complexity.


Consistency is more valuable than individual coding style.

A predictable codebase is easier to understand than one that reflects multiple personal preferences.

Contributors should follow established patterns unless there is a compelling architectural reason not to.


When making engineering decisions, evaluate options using the following priorities.

Priority Principle
1 Correctness
2 Simplicity
3 Readability
4 Maintainability
5 Testability
6 Performance
7 Flexibility

Performance and flexibility should never come at the expense of correctness or maintainability without clear justification.


We actively avoid introducing unnecessary complexity.

Examples include:

  • unnecessary abstractions
  • unnecessary interfaces
  • framework-driven architecture
  • hidden side effects
  • mutable shared state
  • excessive inheritance
  • over-engineering
  • speculative design
  • unnecessary data mapping
  • unnecessary design patterns

Every additional layer should have a measurable architectural purpose.


AI is a productivity tool—not an architect.

Generated code should always follow this philosophy.

When generating code:

  • prioritize readability over cleverness
  • preserve architectural boundaries
  • avoid introducing unnecessary abstractions
  • generate immutable models where possible
  • prefer explicit implementations
  • follow existing project conventions
  • avoid speculative optimizations

If multiple implementations are possible, prefer the simplest solution that aligns with this philosophy.


The goal is not to write the most sophisticated software.

The goal is to build software that remains understandable, adaptable and reliable for years to come.

Every design decision should move the codebase towards greater simplicity, clearer boundaries and higher confidence.