ZJH

Turning Business Rules Into A Running System: A Practical Backend Architecture Guide

A practical backend architecture guide covering APIs, service layers, databases, cache, async jobs, reliability, observability, and deployment.

Backend engineering is not “write endpoints and query the database.” A backend architecture turns business rules into a system that can run, be verified, be debugged, and evolve over time.

The frontend owns the experience loop. The backend owns the business behavior loop:

  • Is the request valid?
  • Is the user allowed to do this?
  • Are the business rules consistent?
  • Was the data durably written?
  • What happens when an external system fails?
  • Is a repeated request safe?
  • Can production issues be diagnosed?

This article maps the backend pieces that matter in real projects.

What Backend Architecture Solves

The backend’s core value is maintaining system facts.

In an order system, “payment succeeded” is not just a status on a screen. It is a set of facts that must be protected:

  • Order status changed.
  • Payment record saved.
  • Inventory reduced.
  • Coupon redeemed.
  • Notification sent.
  • Audit log recorded.
  • Duplicate callbacks did not charge twice.

These are not frontend responsibilities. The backend must place them inside reliable boundaries.

How Backend Architecture Evolves

Simple MVC

Many projects start with MVC:

Controller -> Model -> Database
View / API response

It works well for small projects. The path is short and easy to learn. As business complexity grows, controllers often become too large: validation, permissions, transactions, external calls, and response formatting all end up in one place.

Service Layer

The next stable step is a service layer:

Controller
  -> request validation
  -> auth context
  -> Service
      -> business rules
      -> transaction
      -> repository / external client
  -> response

The controller owns the HTTP boundary. The service owns business actions. The repository or data access layer owns persistence. This structure is not new, but it is durable.

Modular Monolith

When the product grows, microservices are not the only answer. A better intermediate shape is often a modular monolith:

modules/
  users/
  projects/
  billing/
  notifications/

Each module owns its services, data access, interfaces, and tests. Deployment remains one application, but code boundaries become clearer.

For many teams, this is more reliable than early microservices.

Distributed Systems

Microservices or distributed systems become useful when team structure, traffic, deployment cadence, or failure isolation truly require them.

But splitting services adds:

  • Network failure.
  • Distributed transaction problems.
  • Interface versioning.
  • Tracing.
  • Service discovery.
  • Deployment orchestration.
  • Data consistency and compensation.

Without those capabilities, microservices are not an upgrade. They are complexity spent too early.

API Design

APIs are contracts between the frontend and backend.

A good API answers:

  • What is the resource?
  • What is the action?
  • What does success return?
  • What does failure return?
  • How are permission errors and business errors separated?
  • How do pagination, sorting, and filtering work?
  • Which fields can be null?

REST Basics

REST is not random URLs. A resource might look like this:

GET /projects
GET /projects/:id
POST /projects
PATCH /projects/:id
DELETE /projects/:id
POST /projects/:id/archive

The first endpoints are resource operations. The last one is a business action. Do not force every action into CRUD. State transitions such as archive, publish, and cancel are often clearer as explicit action endpoints.

Error Shape

Error format should be stable:

{
  "code": "PROJECT_NAME_TAKEN",
  "message": "Project name already exists.",
  "fields": {
    "name": "This name is already used."
  },
  "requestId": "req_123"
}

The frontend needs code for branching. The user needs message. Debugging needs requestId.

Versioning And Compatibility

API changes should be backward compatible when possible.

Useful rules:

  • Adding fields is usually safe.
  • Removing fields is risky.
  • Changing field meaning is more dangerous than renaming.
  • Large changes can move to /v2 or a new endpoint.
  • Public interfaces should have contract tests or schemas.

Service Layering

A practical backend can start with this structure:

routes / controllers
  -> HTTP input, auth context, response format

services
  -> use cases, transactions, rules, domain actions

repositories
  -> data reads and writes

clients
  -> third-party APIs, payment, messages, email

jobs
  -> async work, retries, scheduled tasks

observability
  -> logs, metrics, traces

Do not create empty layers for aesthetics. But when logic grows, these boundaries keep the system understandable.

Database Design

The database is the source of truth. One of the most important backend decisions is which rules belong in the database and which rules belong in the business layer.

Relational Database As Default

Most business systems can start with PostgreSQL or MySQL.

Reasons:

  • Reliable transactions.
  • Clear constraints.
  • Strong query capabilities.
  • Mature ecosystem.
  • Maintainable data models.

When unsure, a relational database is usually safer than starting with a specialized NoSQL store.

Constraints Should Not Only Live In Code

Put enforceable constraints in the database:

  • Primary keys.
  • Unique constraints.
  • Foreign keys.
  • Not-null constraints.
  • Indexes.
  • Check constraints.

Code validation gives better user messages. Database constraints provide the last line of defense.

Migrations Must Be Repeatable

Schema changes should be versioned.

A team should know:

  • Local, test, and production use the same migrations.
  • Migrations can run through CI or deployment.
  • Production changes are reviewed for locks and large-table impact.
  • Rollback strategy is considered before release.

Cache And Queues

Cache and queues are useful. Neither should be the first reaction.

Cache

Cache solves a specific read performance problem.

Before adding cache, ask:

  • Is the slow part database query, network, serialization, or repeated frontend requests?
  • How stale can data be?
  • Who invalidates it?
  • Can stampedes or penetration happen?

Cache is not a cure for poor data modeling.

Queues

Queues fit asynchronous, slow, retryable work:

  • Sending email.
  • Generating reports.
  • Processing images.
  • Handling payment callbacks.
  • Syncing external systems.

Queued jobs need state, retry limits, idempotency keys, and failure records. Otherwise they only hide errors from the request path.

Authentication, Authorization, And Audit

Authentication answers “who are you.” Authorization answers “what can you do.” Audit answers “what did you do.”

Keep them separate:

Authentication
  -> session / token / identity

Authorization
  -> role / permission / ownership / policy

Audit
  -> actor / action / target / time / request id

Internal systems also need permissions and audit trails. “Only our team uses it” is not a security model.

Reliability

Backend reliability often hides in details.

Idempotency

Repeating the same request should not charge, ship, or create twice.

Common approaches:

  • Client sends an idempotency key.
  • Backend stores the result.
  • External callbacks are deduplicated by event ID.
  • State transitions check current state.

Timeouts And Retries

External calls must have timeouts. Retries must have limits. Failures must be observable.

A call without a timeout can turn one slow service into a slow failure for the whole system.

Rate Limiting

Rate limiting is not only for huge traffic. Login, verification codes, report exports, and expensive queries all need protection.

Observability

After deployment, the most important question is whether the team can understand what happened.

At minimum, a backend needs:

  • Structured logs.
  • Request IDs.
  • Error reporting.
  • Key metrics.
  • Slow query records.
  • Deployment version records.

When the system has multiple services, distributed tracing becomes important. OpenTelemetry helps connect logs, metrics, and traces around a request.

Deployment And Configuration

Configuration should not be hard-coded. Database URLs, API keys, third-party secrets, and feature flags should be injected per environment.

Common environments:

local
test
staging
production

Each environment needs clear configuration sources, databases, logs, and deployment permissions.

Docker defines the runtime contract. CI makes tests and builds automatic. A deployment platform makes release and rollback repeatable.

A Good Backend Default

For a normal business system, I would start here:

Runtime
  -> Node.js / Java / Python / Go based on team experience

Database
  -> PostgreSQL

API
  -> REST + OpenAPI or typed contracts

Structure
  -> controller + service + repository

Async
  -> add a queue only when needed

Cache
  -> avoid until a clear slow path appears

Delivery
  -> Docker + CI + staging + logs

This is not flashy. It covers the first phase of most real projects.

Further Reading