Designing the System
Writing Our First RFC
আপনি একটি 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.