Designing the System

Writing Our First RFC

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

এখন পর্যন্ত আমরা Order Management Backend-এর জন্য যথেষ্ট technical context তৈরি করেছি।

আমরা জানি:

  • business কী চায়
  • v1 scope কী
  • কোন behaviour mandatory
  • major domain concepts কী
  • responsibility boundaries কোথায়
  • system modular monolith হবে
  • PostgreSQL application state store করবে
  • Order creation এবং cancellation local transactional workflow
  • Payment external system boundary cross করবে
  • authentication existing identity capability থেকে আসবে

এখন এই reasoning শুধু আমাদের মাথায় রেখে implementation শুরু করা ঠিক হবে না।

Team-এর অন্য engineer-দের design review করার সুযোগ দিতে হবে।

এই জায়গায় আমরা লিখব আমাদের প্রথম RFC


What Is an RFC?

RFC সাধারণত Request for Comments বোঝায়।

Software engineering team-এ RFC হলো এমন একটি technical proposal যেখানে implementation শুরু করার আগে একটি problem, proposed design, trade-offs, risks এবং open questions document করা হয়।

Purpose:

Think
    ↓
Write
    ↓
Review
    ↓
Improve
    ↓
Implement

RFC-এর goal approval ceremony তৈরি করা নয়।

Goal হলো:

Important technical decisions code-এর মধ্যে লুকিয়ে যাওয়ার আগে reviewable করে তোলা।


Why Write Before Coding?

ধরা যাক আমরা RFC না লিখে implementation শুরু করলাম।

Engineer A ধরে নিল:

Product owns inventory quantity.

Engineer B ধরে নিল:

Inventory is a separate concept.

Engineer C payment logic সরাসরি OrderService-এর মধ্যে provider HTTP call দিয়ে লিখল।

আরেকজন Product, Inventory এবং Order আলাদা service বানানোর কথা ভাবল।

সবাই technically code লিখছে।

কিন্তু shared design নেই।

RFC এই ধরনের divergence early detect করতে সাহায্য করে।


RFC Is Not a Final Truth

RFC implementation-এর আগে আমাদের best current proposal।

এটি immutable contract নয়।

Review-এর সময় design change হতে পারে।

Implementation-এর সময় নতুন evidence আসতে পারে।

তখন RFC update করা যায়।

Important হলো:

Decision changed
    ↓
Reason recorded

not:

Architecture silently drifted.

When Does an RFC Make Sense?

সব ticket-এর জন্য RFC দরকার নেই।

Example:

Fix typo in API documentation

RFC unnecessary।

Add a simple validation rule

probably ticket + PR enough।

কিন্তু আমাদের current project involve করছে:

multiple domain capabilities

inventory consistency

transactions

authentication

external payment integration

persistent architecture

তাই initial system design-এর জন্য RFC justified।


RFC vs Ticket

Ticket:

BACKEND-112
Implement Order Creation Workflow

বলছে:

কোন bounded work implement করতে হবে?

RFC বলছে:

overall design কী এবং কেন?

Ticket execution unit।

RFC technical reasoning artifact।


RFC vs ADR

RFC usually broader proposal।

ADR usually one important decision record করে।

For example:

RFC:

How should the initial Order Management Backend be designed?

ADR:

Why are we using a modular monolith instead of microservices?

আমরা next lesson-এ ADR নিয়ে detail করব।


Our RFC Structure

আমাদের RFC lightweight but complete হবে।

Structure:

Title

Status

Context

Goals

Non-Goals

Requirements Summary

Engineering Context

Proposed Architecture

Domain Model

Major Workflows

Persistence

Transactions and Consistency

Security

Payment Integration

Error Handling

Observability Considerations

Alternatives Considered

Risks

Open Questions

Rollout / Implementation Plan

সব company একই template ব্যবহার করে না।

এই structure আমাদের current design-এর জন্য appropriate।


RFC: Order Management Backend

এখন আমরা actual RFC লিখি।


RFC-001: Initial Order Management Backend Architecture

Status

Proposed

RFC review complete হলে status হতে পারে:

Accepted

বা:

Superseded

যদি later নতুন design replace করে।


Context

Company একটি new Order Management Backend তৈরি করছে।

System-এর initial version customer-কে:

browse available products

create orders

view previous orders

cancel eligible unpaid orders

pay for orders

support করবে।

Administrators:

manage products

manage inventory

করতে পারবে।

Backend Java এবং Spring Boot ব্যবহার করবে।

PostgreSQL transactional persistence হিসেবে থাকবে।

Authentication existing identity capability থেকে আসবে।

Payment external payment provider-এর মাধ্যমে process হবে।


Problem

আমাদের এমন একটি backend architecture প্রয়োজন যা:

core ordering behaviour correctly implement করতে পারে

inventory consistency protect করতে পারে

historical order information preserve করতে পারে

customer ownership enforce করতে পারে

payment provider integrate করতে পারে

future changes safely support করতে পারে

while avoiding unnecessary distributed-system complexity।


Goals

RFC-এর goals clearly define করা গুরুত্বপূর্ণ।

আমাদের goals:

G1
Provide the core v1 ordering workflows.

G2
Keep Product, Inventory, Order, and Payment responsibilities clear.

G3
Preserve historical order pricing.

G4
Prevent invalid inventory state during successful ordering.

G5
Ensure local multi-state workflows are atomic.

G6
Enforce customer ownership and administrative permissions.

G7
Keep payment-provider-specific behaviour isolated from Order domain logic.

G8
Keep the system simple enough to develop and operate as one application.

G9
Maintain testable and reviewable code boundaries.

Non-Goals

Non-goals equally important।

This RFC does not design:

NG1
Microservices architecture

NG2
Kafka or event-driven infrastructure

NG3
Redis or distributed caching

NG4
Multiple warehouses

NG5
Shipping or fulfilment

NG6
Discounts or coupons

NG7
Refunds

NG8
Returns

NG9
Multiple currencies

NG10
Multiple payment providers

NG11
Customer registration or password management

NG12
Cart functionality

NG13
Event Sourcing or CQRS

These may become future requirements।

They are not part of current v1।


Requirements Summary

Product

Customers can browse products available for ordering.

Administrators can create, update, and deactivate products.

Inactive products cannot be used for new orders.

Current product price may change over time.

Inventory

Inventory is associated with a product.

Inventory quantity cannot be negative.

Customers cannot successfully order
more quantity than is available.

Administrators can adjust inventory.

Orders

An order belongs to one customer.

An order contains at least one item.

Each item has a positive quantity.

A product may appear at most once
in one order request.

Backend determines product prices.

Purchase-time unit price is preserved
inside the historical order.

Order creation is all-or-nothing.

Order Lifecycle

v1 lifecycle:

UNPAID
  ├── payment success ──▶ PAID
  └── cancellation ─────▶ CANCELLED

Unsupported:

PAID → CANCELLED

CANCELLED → PAID

Payment

Only eligible unpaid orders can be paid.

Successful payment marks the order paid.

Failed payment must not mark the order paid.

The same successful payment must not be applied twice.

Engineering Context

The service will use:

Java
Spring Boot
Gradle
REST / JSON
PostgreSQL
Flyway
Git
Pull Requests
CI
Containerized deployment

External dependencies:

Existing Identity Capability

External Payment Provider

The system is one deployable backend application।


Proposed Architecture

The system will be implemented as a:

modular monolithic Spring Boot application organized around business capabilities.

Main capabilities:

Product

Inventory

Order

Payment

Security / Customer Context

Each capability will separate responsibilities where needed:

API

Application

Domain

Persistence / Integration

Architecture Overview

                    Existing Identity
                           │
                           ▼
Client
  │
  ▼
HTTP / Security
  │
  ▼
Application Workflows
  │
  ├───────────────┐
  ▼               ▼
Domain         Payment Integration
  │               │
  ▼               ▼
Persistence    Payment Provider
  │
  ▼
PostgreSQL

Dependency Direction

Expected direction:

HTTP
  ↓
Application
  ↓
Domain

Application workflows may also depend on:

Repository boundaries

Payment Gateway

Security identity context

Domain objects should not directly depend on:

Spring MVC

HTTP request objects

JPA repository implementations

Payment provider JSON

Security token parsing

Capability-Oriented Source Organization

Initial direction:

product/
inventory/
order/
payment/
security/
shared/

Within a capability, structure may evolve into:

api/
application/
domain/
persistence/

only where useful।

We will avoid creating empty layers or abstractions without responsibility।


Domain Model

Product

Product is a domain entity।

It owns:

stable identity

current product information

current price

active/inactive ordering state

Product does not own Inventory or historical order pricing।


Inventory

Inventory is a separate stateful concept associated with Product।

It owns:

current available quantity

Invariant:

quantity >= 0

Inventory changes independently from Product metadata।


Customer Identity

Customer authentication is externally owned।

Our application uses stable authenticated identity for:

order ownership

customer-scoped operations

We do not build customer password/account lifecycle।


Order

Order is the central domain entity and aggregate-root candidate।

It owns:

stable identity

customer ownership reference

order lifecycle

order items

historical order meaning

order-level consistency

Order Item

Order Item belongs to Order।

It contains:

product reference

quantity

purchase-time unit price

Order Item has no independent public lifecycle in v1।


Payment

Payment remains a separate application/integration concern।

Exact persistence model will be finalized when payment-provider behaviour is implemented।

The design must support:

confirmed success

failure

duplicate-success protection

relevant provider reference

without leaking provider protocol into Order logic।


Historical Pricing

Product current price and Order Item historical price represent different facts।

Example:

Product current price: €25

Historical Order Item:
unit price: €20

Therefore Order Item stores purchase-time price।

Historical order total must not change when Product price changes।


Major Workflow: Create Order

Conceptual flow:

Receive request
    ↓
Identify authenticated customer
    ↓
Validate requested items
    ↓
Load Products
    ↓
Verify Products are orderable
    ↓
Load Inventory
    ↓
Verify sufficient quantity
    ↓
Use current Product prices
    ↓
Create Order + Order Items
    ↓
Decrease Inventory
    ↓
Persist Order and Inventory

All local mutations belong to one business operation।


Create Order Transaction Boundary

Requirement:

Order creation is all-or-nothing.

Therefore:

Order persistence
+
Order Item persistence
+
Inventory decrease

must commit together।

The application-level CreateOrder workflow will define the transaction boundary।

Spring transaction management will implement that boundary।


Inventory Concurrency

A simple:

read quantity
check quantity
write quantity

flow is not enough under concurrency।

Example:

Inventory = 1

Request A reads 1
Request B reads 1

Both attempt to buy 1

Design requirement:

Inventory consumption must remain correct under concurrent order creation.

Exact persistence strategy is intentionally not finalized in this RFC।

Implementation must choose a concurrency-safe PostgreSQL approach।

Possible implementation techniques may include locking or atomic update semantics, but the choice will be documented when implemented।


Major Workflow: Cancel Order

Conceptual flow:

Receive cancellation request
    ↓
Identify customer
    ↓
Load Order
    ↓
Verify ownership
    ↓
Order validates cancellation transition
    ↓
Restore Inventory
    ↓
Persist Order + Inventory

Cancellation is supported only for unpaid orders in v1।


Cancellation Transaction Boundary

These local changes:

Order → CANCELLED
+
Inventory restored

must commit together।

If one fails:

neither should remain committed

Therefore cancellation is one local database transaction।


Major Workflow: View Order History

Flow:

Authenticated customer
    ↓
Query orders by customer identity
    ↓
Return bounded/paginated history

Historical Order Items use recorded purchase-time prices rather than current Product prices।

Customer may only access their own orders।


Major Workflow: Product Browsing

Product browsing may combine:

Product state
+
Inventory state

to determine whether an item is currently orderable।

Product active state and Inventory quantity remain separate concepts।

The API must use bounded/paginated retrieval।


Major Workflow: Payment

Conceptual flow:

Receive payment request
    ↓
Identify customer
    ↓
Load Order
    ↓
Verify ownership
    ↓
Verify Order can be paid
    ↓
Call Payment Gateway
    ↓
External Payment Provider
    ↓
Process result
    ↓
On confirmed success:
apply payment result safely
and transition Order to PAID

Payment Is Not a Local Atomic Transaction

Payment crosses an external system boundary।

PostgreSQL cannot atomically roll back:

payment provider success

if a later local database update fails।

Therefore the payment workflow requires dedicated failure and idempotency handling।

This RFC establishes the risk but does not pretend a local @Transactional annotation solves it।

Detailed provider behaviour will be finalized during payment integration implementation।


Payment Integration Boundary

Application layer will depend on an application-facing payment capability।

Conceptually:

PayOrder
    ↓
PaymentGateway
    ↓
ProviderPaymentClient
    ↓
External Provider

PaymentGateway exposes application-relevant behaviour।

Provider client owns:

provider URL

provider credentials

HTTP request format

HTTP response format

provider-specific error codes

timeout handling

Order domain must not depend on those details।


Persistence Strategy

Application-owned state will be stored in PostgreSQL।

Expected persistent concepts:

Product

Inventory

Order

Order Item

Payment-related persistence will be added only if payment design requires it।

Schema changes will use Flyway migrations।


Persistence Principles

Database schema should:

support required relationships

protect important persisted invariants

support current query patterns

preserve historical order data

Potential database integrity mechanisms include:

NOT NULL

FOREIGN KEY

CHECK

UNIQUE

when appropriate।

Exact schema comes during persistence implementation।


Product Deactivation

Products will be deactivated rather than physically deleted in v1।

Reason:

future ordering should stop

historical orders must remain valid

Historical Order Items must not depend on Product remaining active।


Security Design

Existing identity infrastructure authenticates requests।

Our service is responsible for:

accept authenticated identity

enforce application permissions

enforce resource ownership

Administrative Authorization

Product and Inventory administrative operations require administrator authority।

Regular customers must not access these write operations।

Spring Security may enforce endpoint-level role requirements।


Customer Ownership

Application workflows enforce resource ownership।

For example:

Customer A

must not:

view

cancel

pay

Order belonging to Customer B।

Ownership checks cannot rely solely on route-level authentication because they require persisted Order data।


API Responsibility

HTTP layer will:

parse REST/JSON requests

perform transport validation

obtain authenticated identity

invoke application workflows

map results to response DTOs

translate expected failures to consistent API errors

HTTP layer will not own business workflows।


Error Handling

We will distinguish:

Business / Application Failure

from:

Unexpected Operational Failure

Examples of expected failures:

Product not found

Insufficient inventory

Order cannot be cancelled

Order cannot be paid

Resource ownership violation

Unexpected failures:

database unavailable

unexpected exception

API layer will map expected application failures consistently।

Operational failures will be observable and will not be disguised as normal business outcomes।


Observability Considerations

Detailed observability is later course scope, but architecture must support it।

Application will eventually provide:

structured logs

request correlation

health checks

metrics

Domain objects should not directly depend on logging or metrics frameworks।

Instrumentation belongs at appropriate application/infrastructure boundaries।


Testing Strategy Implications

Architecture supports several test levels।

Domain Tests

Protect:

Order lifecycle

Order total

Inventory quantity rules

Application Workflow Tests

Protect:

Create Order coordination

Cancellation behaviour

ownership rules

payment workflow decisions

Persistence Integration Tests

Protect:

JPA mappings

queries

Flyway migrations

database constraints

concurrency-sensitive behaviour

API Integration Tests

Protect:

HTTP contract

validation

security

error responses

Detailed testing strategy comes later।


Alternatives Considered

A good RFC should mention realistic alternatives—not every technology that exists।


Alternative 1 — Microservices

Potential structure:

Product Service

Inventory Service

Order Service

Payment Service

Benefits

Possible independent deployment and scaling।

Costs

Would introduce:

network failure

distributed consistency

service coordination

additional deployment complexity

cross-service observability

Decision

Rejected for v1।

Current requirements do not justify distributed architecture।

One application gives us simpler local consistency and operations।


Alternative 2 — Single Layered Package Structure

Example:

controller/
service/
repository/
entity/

Benefits

Simple and familiar at small scale।

Costs

As capabilities grow, related code becomes scattered across technical directories।

Decision

Prefer capability-oriented organization with responsibility separation inside each capability when useful।


Alternative 3 — Product Owns Inventory Quantity

Model:

Product
└── quantity

Benefits

Fewer concepts।

Costs

Product metadata and Inventory have different lifecycle, mutation frequency, and invariants।

Order creation and cancellation directly manipulate Inventory behaviour।

Decision

Keep Inventory as a separate stateful concept।


Alternative 4 — Use Current Product Price for Order History

Model:

Order Item stores only Product ID

Historical price loaded from Product later।

Benefit

Less stored data।

Problem

Product price change would rewrite historical order meaning।

Decision

Rejected।

Order Item preserves purchase-time unit price।


Alternative 5 — Provider HTTP Logic Directly in Order Service

Benefit

Fewer classes initially।

Costs

Mixes:

provider protocol
business rules
HTTP behaviour
order lifecycle

and tightly couples Order workflow to provider details।

Decision

Use an explicit payment integration boundary।


Risks

Risk 1 — Inventory Overselling

Concurrent requests may attempt to consume the same quantity।

Mitigation direction:

database transaction
+
concurrency-safe inventory persistence

Exact implementation pending।


Risk 2 — Payment Partial Failure

Provider may confirm payment while local update fails।

This cannot be solved using only a local database transaction।

Mitigation requires provider-aware idempotency/failure design during payment implementation।


Risk 3 — Authorization Mistakes

Incorrect ownership checks could expose one customer's order to another।

Mitigation:

central authenticated identity context

application-level ownership checks

integration tests

Risk 4 — Historical Data Drift

Using live Product data incorrectly could change historical Order meaning।

Mitigation:

store purchase-time unit price in Order Item

Risk 5 — Architecture Overgrowth

Commerce domain easily attracts unrelated features and infrastructure।

Mitigation:

explicit non-goals

small v1 scope

no speculative distributed infrastructure

Open Questions

The RFC should not hide genuine open questions।

Current open questions:

OQ-1
Which PostgreSQL concurrency mechanism
will protect inventory consumption?

OQ-2
What exact Java representation will be used
for monetary values?

OQ-3
Will Order total be persisted or always derived?

OQ-4
What exact payment state/reference data
must be persisted?

OQ-5
What provider idempotency guarantees are available?

OQ-6
What exact REST error contract will the service expose?

OQ-7
What exact ID generation strategy will be used?

These do not block accepting the high-level architecture unless a reviewer identifies one as fundamental।

They can be resolved during implementation-specific design।


Implementation Plan

High-level implementation order follows our backlog।

Phase 1
Application bootstrap

Phase 2
PostgreSQL + Flyway foundation

Phase 3
Product domain and persistence

Phase 4
Product API / browsing

Phase 5
Inventory domain and persistence

Phase 6
Order domain and persistence

Phase 7
Order creation workflow

Phase 8
Order history and cancellation

Phase 9
Security integration and authorization

Phase 10
Payment integration

Phase 11
Production-readiness work

Phase 12
Containerization and CI

Testing and relevant documentation are part of each phase rather than postponed to the end।


Rollout Approach

This is a new service, so initial rollout does not require migrating an existing Order Management system।

Still, delivery will be incremental।

We will first establish working vertical capabilities rather than implementing every layer for the entire system upfront।

Example initial milestone:

Application runs

PostgreSQL works

Product persistence works

Products can be exposed through API

Then progressively add Inventory এবং Order workflows।


Proposed Decision

The proposal is:

Implement the Order Management Backend as a modular monolithic Spring Boot application organized around business capabilities. Keep HTTP, application workflow, domain, persistence, security, and payment integration responsibilities distinct where useful. Use PostgreSQL for application-owned transactional state, with Flyway-managed schema evolution. Keep Order and Inventory as separate domain concepts coordinated transactionally for order creation and cancellation. Preserve historical purchase-time pricing within Order Items. Use the existing identity capability for authentication and enforce resource ownership inside application workflows. Isolate payment-provider protocol behind an application-facing integration boundary.


What Should Reviewers Look For?

An RFC should make review easy।

Reviewer should ask:

Does this satisfy the agreed requirements?

Are domain boundaries reasonable?

Is anything unnecessarily complex?

Are important invariants protected?

Are transaction boundaries correct?

Are system boundaries clear?

Does Payment integration acknowledge partial failure?

Are security responsibilities clear?

Are any assumptions hidden?

Are important alternatives missing?

Are any non-goals actually required for v1?

This is much more useful than comments about formatting।


Example Review Comment

Reviewer might say:

The current proposal says Product browsing combines
Product and Inventory.

Please clarify whether products with zero inventory
are excluded entirely or returned as unavailable.

This is a valuable comment।

It reveals unresolved product/API behaviour।

Another:

The payment section identifies duplicate-success risk,
but we should confirm provider idempotency support
before implementation begins.

Also valuable।


RFC Review Is Not Code Review

Code review asks:

Is this implementation correct?

RFC review asks earlier:

Are we solving the problem in the right way?

Fixing a design issue in RFC:

edit document

Fixing it after implementation:

rewrite code

rewrite tests

change schema

change API

review again

Potentially much more expensive।


Avoid Writing an RFC That Is Too Vague

Bad:

We will use Spring Boot and best practices.

The system will be scalable and maintainable.

This says almost nothing।

A useful RFC explains:

boundaries

ownership

workflows

transactions

risks

trade-offs

Avoid Writing Implementation Code Inside the RFC

Other extreme:

50 pages of exact Java classes,
method bodies, SQL statements,
and every annotation

before coding।

That is usually too detailed।

Our RFC should guide implementation, not replace it।


Avoid Fake Alternatives

We do not need to write:

PostgreSQL vs Cassandra vs MongoDB vs DynamoDB

because PostgreSQL is already part of our engineering context and current requirements fit it।

Alternatives section should cover real decisions।


Avoid Pretending Risks Are Solved

Payment partial failure is a good example।

Bad RFC:

We will use @Transactional,
therefore payment is consistent.

Wrong because external provider is outside PostgreSQL।

Better:

This is a known cross-system consistency risk.
Detailed idempotency and failure handling
will be designed against actual provider behaviour.

This is more professional।


RFC Status Lifecycle

A simple RFC lifecycle:

Draft
  ↓
Proposed
  ↓
Review
  ↓
Accepted

Later:

Accepted
   ↓
Superseded

if a newer decision replaces it।

We do not need a complicated governance system।


Where Should RFCs Live?

Our repository plan already included:

docs/
└── rfcs/

So:

docs/rfcs/001-order-management-architecture.md

could contain this RFC।

Keeping it with the repository makes design history discoverable alongside code।


RFC and Pull Requests

When implementation starts, PR descriptions can reference:

RFC-001

For example:

Implements the Product persistence portion
of RFC-001.

This improves traceability।


RFC and Tickets

Likewise tickets can reference the design।

Example:

BACKEND-112
Implement Order Creation Workflow

Design:
RFC-001 — Initial Order Management Backend Architecture

Now:

Requirement
    ↓
RFC
    ↓
Ticket
    ↓
PR
    ↓
Code

becomes traceable।


When Should the RFC Be Updated?

Update RFC if implementation reveals that an architectural statement is no longer true।

For example:

Original:

Order total will always be derived.

Later evidence shows we need to persist it।

If that change is meaningful to the design, update the RFC or record a new ADR।

Do not silently let documentation become false।


RFC Is Not a Substitute for ADRs

Some decisions inside this RFC deserve dedicated historical records because they may remain relevant long after implementation।

Examples:

Why modular monolith?

Why PostgreSQL?

Why purchase-time pricing snapshot?

Why isolate payment integration?

The RFC gives current design context।

ADR can preserve specific decision rationale over time।

That is our next lesson।


Engineering Principle

The core principle from this lesson:

Write down important technical reasoning before implementation makes the decision expensive to change.

Another:

An RFC should make requirements, decisions, trade-offs, risks, and unknowns visible—not pretend uncertainty does not exist.

And:

Documentation is valuable when it improves engineering decisions, not when it merely increases the number of documents.


Summary

In this lesson, we:

  • defined what an RFC is and why teams use it
  • distinguished RFCs from tickets and ADRs
  • created our first real technical proposal
  • documented Goals and Non-Goals
  • captured the modular monolith architecture
  • documented Product, Inventory, Order, Customer Identity, Order Item, and Payment boundaries
  • documented Order creation and cancellation transaction requirements
  • explicitly recorded inventory concurrency risk
  • explicitly recorded payment partial-failure risk
  • documented security and ownership responsibilities
  • captured realistic alternatives and why they were rejected
  • recorded open implementation questions instead of inventing answers
  • connected the RFC to our backlog and future Pull Requests
  • established that RFC review happens before expensive implementation decisions are locked into code

Our initial system design is now reviewable by another engineer.

Next lesson:

Architecture Decision Records

There we will learn how to record individual durable technical decisions separately from the broader RFC, and we will create the first ADRs for the Order Management Backend.