Design & Architecture

Separation of Concerns & Coupling

Foundational

Software stays easy to change only if each part has one clear job and knows as little as possible about the others. If you mix responsibilities together, every change has unexpected effects. If you keep them separate, you can understand, test, and change one thing without breaking five. Aim for high cohesion inside a unit and loose coupling between units.

Separation of concerns means a piece of code does one clear thing: handle HTTP, enforce a business rule, or talk to the database. It does not mix all three. Coupling is how much one part depends on the internals of another. You want high cohesion (related things together) and low coupling (few, narrow, explicit dependencies). This is what lets a system grow without becoming too complex to manage.

This is not design for its own sake. In our system it has direct effects. Business rules and security checks mixed into controllers and SQL are hard to test, easy to bypass, and easy to get subtly wrong. Clear layers, and a clear place for each rule, make the important logic visible, testable, and applied consistently.

Give each part one job

Everything in the controller [HttpPost] public IActionResult Approve(Guid id) {
var c = conn.QuerySingle("SELECT * FROM Customers WHERE Id=@id", new{id});
if (c.Risk == "High") return BadRequest(); // business rule
conn.Execute("UPDATE Customers SET Status='Approved' WHERE Id=@id", new{id});
return Ok();
}

Transport, business rule, and persistence are merged. You cannot unit-test the approval rule without HTTP and a database, and the same rule will drift the moment it is needed elsewhere.

Separated, testable layers [HttpPost] public IActionResult Approve(Guid id) =>
_onboarding.Approve(id, User); // controller: just transport

// application layer: the rule lives here, unit-testable in isolation
public Result Approve(Guid id, IPrincipal user) { /* rule + repo call */ }

The controller only adapts HTTP. The business rule is one testable method. Persistence sits behind the repository. Each can change without disturbing the others.

Keep coupling loose and explicit

Self-review checklist

Why it matters: Coupling is what makes a codebase slow and risky to change. A small edit breaks something far away, and nobody dares to refactor. Clear separation keeps logic testable and changes local. That is what lets us move fast safely and keep critical business and security rules correct and in one place.