Building REST APIs
HTTP Through a Backend Engineer's Eyes
আপনি একটি free preview lesson দেখছেন।
আমরা এখন পর্যন্ত application-এর ভেতরের structure তৈরি করেছি:
Handler
↓
UseCase
↓
Domain
↓
Repository
এখন external client আমাদের application-এর সঙ্গে communicate করবে কীভাবে?
আমাদের current architecture-এর জন্য answer:
HTTP
+
JSON
+
REST-style API
কিন্তু HTTP ব্যবহার করা মানে শুধু:
GET
POST
PUT
DELETE
মুখস্থ করা নয়।
একজন backend engineer-এর জন্য HTTP হলো:
Client এবং server-এর মধ্যে একটি behavioural contract.
Request communicate করে:
আমি কোন resource নিয়ে কাজ করছি?
আমি কী operation চাই?
এই operation repeat করলে কী হওয়া উচিত?
Request সফল হলে কী response পাব?
Failure হলে কীভাবে distinguish করব?
এই semantics ভুল design করলে technically working API-ও confusing, unsafe, এবং hard to integrate হতে পারে।
HTTP Is an Application Boundary
আমাদের domain HTTP সম্পর্কে কিছু জানে না।
For example:
order.cancel();
does not know whether request এসেছে:
POST /orders/{id}/cancel
বা অন্য কোনো transport থেকে।
HTTP boundary-এর কাজ:
request গ্রহণ করা
request-এর intent বোঝা
transport data map করা
UseCase invoke করা
result/error HTTP response-এ translate করা
Conceptually:
HTTP Request
↓
Handler
↓
UseCase
↓
Domain
↓
HTTP Response
An HTTP Request Has Several Important Parts
Suppose:
POST /orders
Content-Type: application/json
Authorization: Bearer ...
{
"items": [
{
"productId": "P-100",
"quantity": 2
}
]
}
এখানে আছে:
Method
→ POST
Path
→ /orders
Headers
→ Content-Type
→ Authorization
Body
→ Order creation input
প্রতিটি অংশের আলাদা responsibility আছে।
Method Communicates Intent
HTTP method বলে client কী ধরনের interaction করতে চাইছে।
Common methods:
GET
POST
PUT
PATCH
DELETE
সব method interchangeable নয়।
For example:
GET /orders/123
এবং:
POST /orders/123
technically দুটিই server code trigger করতে পারে।
কিন্তু protocol semantics এক নয়।
API design-এ method meaning respect করা গুরুত্বপূর্ণ।
GET Is for Reading
Typical:
GET /products
means:
Give me a representation of Products.
Similarly:
GET /orders/{id}
means:
Give me this Order representation.
A GET request should not normally cause a business mutation such as:
decrease Inventory
cancel Order
create Payment
change Product price
Why Mutating Through GET Is Dangerous
Imagine:
GET /orders/123/cancel
and server cancels the Order।
This is problematic because GET is commonly treated as a read operation by:
browsers
caches
proxies
monitoring systems
link preview systems
A system may issue GET requests automatically under assumptions that reading does not change business state।
Business mutation should therefore not be hidden behind GET।
Safe HTTP Methods
A safe method is intended for read-like operations that do not request state mutation.
The most relevant example for us:
GET
Safe does not mean:
nothing whatsoever changes internally
A GET may still result in:
access logs
metrics
cache updates
But the client is not requesting a business-state change।
Idempotency
Another important HTTP concept:
If the same request is repeated, does performing it multiple times have the same intended final effect as performing it once?
This is called idempotency।
For example, conceptually:
Set Product active = false
performed once or several times can produce the same final Product state।
But:
Create another Order
repeated twice may create two Orders।
So creation is generally not naturally idempotent।
Idempotency Does Not Mean Same Response
Suppose:
DELETE /something
first call succeeds।
Second call may return a different response because resource is already gone।
The important idea is the intended state effect:
after one request
→ resource absent
after repeated requests
→ resource still absent
Idempotency is about effect, not necessarily byte-for-byte identical responses।
Why Backend Engineers Care About Idempotency
Networks fail in ambiguous ways।
Imagine:
Client
↓
POST /orders
↓
Server creates Order
↓
response connection breaks
Client sees:
timeout
What happened?
Possibilities:
request never reached server
server started but failed
Order successfully created but response was lost
If client retries blindly:
POST /orders
we may create duplicate Orders।
This is why idempotency becomes critical for certain workflows।
We will return to it deeply during Payment integration।
POST
POST commonly represents:
create a subordinate/new resource
trigger a non-idempotent operation
For example:
POST /orders
fits our Order creation workflow naturally।
Client submits:
Order creation intent
Server creates:
new Order
POST Is Not "Any Endpoint That Changes Something"
A weak API might use:
POST /getProducts
POST /updateProduct
POST /deleteProduct
POST /cancelOrder
Everything becomes POST because implementation is easy।
But then HTTP semantics stop helping clients understand behaviour।
Prefer resource-oriented and operation-aware design।
PUT
PUT generally represents replacing the state of a resource at a known URI.
Conceptually:
PUT /resources/{id}
with a complete representation can mean:
Make the resource at this URI look like this representation.
Because replacement is naturally idempotent, repeated identical PUT requests should have the same intended result।
Should We Use PUT for Product Update?
Maybe, but it depends on the API contract।
Suppose Product has:
name
price
active state
and endpoint expects a complete replaceable Product representation।
Then PUT may fit।
But if admin only changes:
price
or:
name
without replacing all fields, PATCH may communicate partial update more naturally।
We will decide concrete endpoints in the API design lesson।
PATCH
PATCH represents partial modification.
Example conceptually:
PATCH /products/{id}
Body:
{
"price": 89.99
}
This means:
Apply this partial change to the existing Product.
But PATCH itself does not automatically guarantee idempotency.
The semantics depend on the patch operation।
Example of Idempotent Partial Update
{
"price": 89.99
}
Meaning:
set price to 89.99
Applying it multiple times yields the same final price।
Example of Non-Idempotent Update Semantics
If API instead means:
{
"increasePriceBy": 10
}
then repeating it changes the state repeatedly:
100 → 110 → 120
Even if transported through PATCH, the operation itself is not idempotent।
Method choice helps communicate semantics, but business operation still matters।
DELETE
DELETE usually represents removal of a resource.
But our Product requirement is important:
Product is not physically deleted.
Product is deactivated.
Therefore:
DELETE /products/{id}
might communicate something stronger than our business behaviour actually does।
We should not choose DELETE simply because an admin wants a Product to stop appearing in ordering flows।
Business language says:
deactivate Product
not:
delete Product
The API should reflect the business semantics appropriately।
HTTP Method Does Not Replace Domain Meaning
For example:
PATCH /products/{id}
could set:
{
"active": false
}
or we might expose an explicit domain operation such as:
POST /products/{id}/deactivate
Both can be defensible depending on contract style।
The correct choice depends on:
resource representation
business operation clarity
future lifecycle behaviour
API consistency
There is no universal rule that every state transition must map to one specific HTTP method pattern।
Resource-Oriented Thinking
REST-style APIs commonly organize endpoints around resources.
Our main resources include:
products
inventory
orders
Potentially payment-related resources/actions later।
Examples:
GET /products
GET /products/{id}
POST /orders
GET /orders/{id}
This is clearer than RPC-like names such as:
/getAllProducts
/createNewOrder
/findOrderById
because HTTP method already carries part of the operation meaning।
URI Should Identify the Thing, Not Implementation
Good:
/orders
/products
Weak:
/orderController/create
/productService/getAll
API consumer should not know internal architecture such as:
Handler
UseCase
Repository
Those are implementation details।
Plural Resource Names
A consistent convention might use:
/products
/orders
rather than mixing:
/product
/orders
/inventoryItem
Consistency matters more than debating every naming convention endlessly।
Path Parameters
For a specific resource:
GET /orders/{orderId}
Example:
GET /orders/O-1001
The path identifies:
which Order
The Handler can parse the path value into an application/domain-friendly:
OrderId
Query Parameters
Query parameters are useful for modifying how a collection/read request behaves।
For example:
GET /products?page=2
or later perhaps:
GET /products?limit=20
depending on pagination design।
They are commonly useful for:
pagination
filtering
sorting
but not every business action belongs in query parameters।
Avoid Mutation Through Query Flags
Weak:
GET /orders/123?cancel=true
The path looks like a read, while a query parameter secretly causes mutation।
Use an operation whose HTTP semantics clearly indicate state change।
Headers
Headers carry metadata about the request/response।
Relevant examples:
Content-Type
Authorization
Accept
request/correlation identifiers
For JSON request:
Content-Type: application/json
communicates:
The request body is JSON.
Authorization Header
Authentication token commonly arrives through:
Authorization: Bearer ...
Our Handler/domain should not manually parse this header everywhere।
Instead:
HTTP
↓
Spring Security
↓
Authenticated identity
↓
Handler / UseCase
Security infrastructure handles protocol authentication।
Business workflow receives application-friendly identity such as:
CustomerId
HTTP Status Codes Are Part of the Contract
A response is not only JSON.
Example:
HTTP/1.1 200 OK
or:
HTTP/1.1 404 Not Found
Status codes communicate broad outcome semantics before the client even interprets the body।
Choosing status codes consistently makes client integrations easier।
2xx — Successful Requests
Common success statuses:
200 OK
201 Created
204 No Content
They mean different things।
200 OK
Suitable when request succeeds and server returns a representation/result।
Example:
GET /orders/O-1001
Response:
200 OK
with Order JSON।
201 Created
Useful when a request successfully creates a new resource।
For example:
POST /orders
successfully creating:
Order O-1001
can naturally return:
201 Created
possibly with the created Order representation and/or location information depending on contract design।
This communicates more precisely than generic 200.
204 No Content
Useful when operation succeeds but there is intentionally no response body.
For example certain update operations may choose:
204 No Content
However, if returning the updated representation is useful, 200 may be better।
Do not use 204 while also trying to return JSON content।
4xx — Client-Side Request Problems
Broadly:
4xx
means the request cannot be successfully fulfilled because of something about the client request/context।
Examples include:
invalid input
missing authentication
insufficient permission
resource not found
business operation not allowed
But the exact status should reflect the failure semantics।
400 Bad Request
Commonly used when request itself is malformed or invalid।
Examples:
invalid JSON
missing required field
quantity = -5
for a request that cannot satisfy the API contract।
Do Not Turn Every Business Failure Into 400
Suppose:
Order does not exist
That is different from:
request JSON is malformed
Likewise:
authentication missing
has distinct semantics।
Status codes allow us to communicate those distinctions।
401 Unauthorized
Despite the name, 401 is commonly used when the request lacks valid authentication credentials.
Conceptually:
Who are you?
could not be established successfully।
For example:
missing token
invalid token
expired/otherwise unacceptable credential
depending on authentication setup।
403 Forbidden
403 is different:
identity may be known
but this caller is not allowed to perform the operation
For example:
CUSTOMER tries to use admin Product-management endpoint
could result in 403।
401 vs 403
Useful mental model:
401
→ valid authentication not established
403
→ authenticated caller lacks permission
Do not use them interchangeably without thought।
404 Not Found
If requested resource does not exist:
GET /orders/O-9999
could result in:
404 Not Found
But resource ownership introduces an interesting security question।
404 vs 403 for Another Customer's Resource
Suppose customer A requests Order owned by customer B।
Two possible API approaches:
403
→ Order exists but caller cannot access it
or:
404
→ do not reveal whether another customer's Order exists
Both patterns exist।
For sensitive customer-owned resources, returning 404 can reduce resource-existence leakage।
The important thing is to make a deliberate, consistent security/API decision later।
Do not casually expose cross-customer resource existence।
409 Conflict
A request may be structurally valid but conflict with current resource/business state.
For example:
cancel a PAID Order
may fit conflict-style semantics because:
request shape is valid
but current Order state does not permit it
Similarly certain concurrency/state conflicts might map here।
Exact error mapping will be designed later।
422 and Similar Choices
Some APIs use 422 Unprocessable Content for semantically invalid requests that are syntactically correct।
Others use 400 for broader validation failures.
The key engineering goal is not to maximize the number of status codes used।
It is:
clear
documented
consistent
error semantics।
We will define our API error model deliberately instead of choosing codes ad hoc per Handler।
5xx — Server-Side Failure
Broadly:
5xx
indicates the server failed to fulfill a valid request because of an internal/server-side problem।
Examples:
unexpected bug
database unavailable
unhandled infrastructure failure
Expected business rejection should generally not become:
500 Internal Server Error
just because it was represented as a Java exception internally।
Exception Does Not Mean 500
This is an important Spring/backend lesson।
Domain may throw:
OrderNotCancellableException
That can be an expected business failure।
Handler/error mapping layer translates it to appropriate HTTP semantics।
Only unexpected failures should normally surface as server errors।
HTTP Response Body
For successful resource response:
{
"id": "O-1001",
"status": "UNPAID",
"total": 90.00
}
For error response, we eventually want a consistent structure rather than random strings such as:
"bad request"
from one endpoint and:
"something went wrong"
from another।
Consistent error response design comes later in this module।
HTTP Contract Is Not the Domain Model
Our domain may use:
OrderStatus.UNPAID
The API could expose:
{
"status": "UNPAID"
}
or another agreed representation।
The Handler/DTO boundary controls that contract।
We should not force domain design to match JSON implementation details accidentally।
Success Does Not Always Mean 200
Weak API behaviour:
every success → 200
every failure → 500
throws away useful HTTP semantics।
Backend engineers should use protocol information to make integrations easier to reason about।
Resources and Business Actions
Some operations map naturally to CRUD-like resource interaction:
create Product
read Product
update Product
Others are explicit business transitions:
cancel Order
pay Order
deactivate Product
Trying to hide every operation inside generic CRUD can sometimes make API meaning worse।
Example: Order Cancellation
Possible design:
POST /orders/{id}/cancel
This clearly expresses a business command।
Another possible design could mutate Order state via:
PATCH /orders/{id}
with:
{
"status": "CANCELLED"
}
But that would give the client the appearance of directly controlling Order lifecycle state।
For our domain:
order.cancel();
is an explicit operation with rules।
An explicit cancellation endpoint is therefore likely to align better with the business model than a generic status setter API।
We will finalize this in endpoint design।
Example: Payment
Similarly:
POST /orders/{id}/pay
can express:
Attempt to pay this Order.
This is not equivalent to:
PATCH /orders/{id}
with:
{
"status": "PAID"
}
The client cannot simply declare an Order paid।
Payment must involve:
authorization
Order eligibility
external Payment Service
confirmed success
The API should not imply otherwise।
HTTP Design Should Preserve Server Authority
Bad:
PATCH /orders/{id}
{
"status": "PAID",
"total": 1.00,
"customerId": "someone-else"
}
This exposes server-owned business state as client-controlled mutable fields।
Better API design allows client to express intent:
create this Order
cancel my Order
pay my Order
while the backend determines valid state transitions।
Command vs State Assignment
This distinction is useful:
"set status to PAID"
is raw state assignment।
"pay this Order"
is a business command।
The domain also models:
order.markPaid();
only after successful provider execution।
HTTP contract should align with that domain meaning।
GET Should Not Be Used for Commands
Avoid:
GET /orders/{id}/pay
or:
GET /orders/{id}/cancel
because those mutate business state।
Use an unsafe/mutating method appropriate to the operation।
Repeating POST Requests
Consider:
POST /orders
twice with the same body।
Without an explicit idempotency mechanism, we should assume:
two requests
may create two Orders
The API must not pretend otherwise।
If product requirements later require retry-safe creation, we would introduce a deliberate idempotency contract।
Payment Is Even More Sensitive
Repeated:
POST /orders/{id}/pay
could have financial consequences।
The domain prevents:
PAID → PAID
locally।
But network retries may happen before local state is confidently known।
That is why Payment integration requires dedicated idempotency handling beyond method choice।
Idempotency Is a System Property, Not Just an HTTP Label
Simply saying:
PUT is idempotent
does not magically make badly implemented code safe।
Suppose a PUT Handler:
increments a counter
on every request।
Its actual effect is not idempotent despite method convention।
Implementation must honour the contract।
HTTP and Caching
GET responses may participate in HTTP caching depending on headers and infrastructure।
We do not need caching for our current API design lesson।
But this is another reason GET should accurately represent read semantics।
Protocol infrastructure makes assumptions based on method meaning।
Request Bodies on GET
Even if some technical stack allows unusual patterns, avoid designing APIs around GET request bodies for normal resource querying।
Use:
path parameters
query parameters
for typical GET request input।
Keep APIs conventional unless there is a strong reason not to।
Content Negotiation
Clients can indicate what media type they send/accept using headers such as:
Content-Type
Accept
Our API will primarily use:
application/json
We do not need a complex content-negotiation strategy for v1।
Keep the contract simple।
HTTP Is Stateless at the Protocol Interaction Level
A REST-style API generally avoids requiring one server instance to remember hidden conversational state between requests.
For example:
Request 1 creates Order
Request 2 pays Order
The second request identifies the Order and authenticated caller through explicit request/application state।
It should not require:
the same application process
remembering some Java object from Request 1
Persistent state belongs in PostgreSQL।
Authentication context comes with the request।
Do Not Store Per-Customer Workflow in Singleton Beans
This connects back to Spring Bean scope।
Bad:
@Component
public class OrderHandler {
private Order currentOrder;
}
HTTP requests from many customers can hit the same Bean।
Per-request/business state must remain in:
method-local values
domain objects
database state
not shared mutable singleton fields।
HTTP Request Is Not a Database Transaction
Another important distinction:
HTTP request boundary
and:
database transaction boundary
are related but not identical concepts।
A UseCase may run transactionally inside one request։
But HTTP itself does not give us database atomicity।
Transactions will be handled deliberately in the business workflow module।
Timeout Does Not Necessarily Mean Operation Failed
This is one of the most important distributed-system realities to understand early।
Client sends:
POST /orders
then times out।
Client knows only:
I did not receive a successful response.
It does not necessarily know:
server did not create the Order.
This distinction becomes critical for retry behaviour।
Error Response vs Lost Response
Case A:
server returns 400
client receives 400
Outcome is known।
Case B:
server commits successfully
network drops response
Client sees timeout but server succeeded।
These are not equivalent।
HTTP API design must eventually account for retry-sensitive operations।
Think in Terms of Observable Contract
Backend implementation may contain:
10 classes
3 repository calls
1 transaction
Client should not need to know any of that।
Client only observes:
method
URI
headers
request body
status code
response body
That is the API contract।
The Contract Should Survive Refactoring
Suppose internal flow changes from:
Handler → UseCase → Repository
to a more optimized persistence implementation later।
If product behaviour is unchanged, public API should not need to change unnecessarily।
Separating transport contract from implementation gives us that flexibility।
API Design Is Product Design Too
Consider Order cancellation.
Questions include:
Which Orders can be cancelled?
How does client request cancellation?
What response indicates success?
What happens if already paid?
What if Order belongs to another customer?
These are not just controller implementation details।
They are part of product behaviour exposed to clients।
Don't Design Endpoints From Repository Methods
Bad approach:
OrderRepository has save()
→ POST /saveOrder
ProductRepository has findAll()
→ GET /findAllProducts
Persistence implementation should not determine the public contract।
Start from use cases/business capabilities।
Don't Design API Around Java Class Names
Avoid:
/createOrderUseCase
/orderHandler
/productEntity
Public API vocabulary should use product/domain language:
/orders
/products
Internal class naming is not part of the client contract।
API Should Be Predictable
If:
GET /products/{id}
returns one Product,
then:
GET /orders/{id}
should follow similar resource conventions where possible।
Consistency reduces documentation burden because clients can predict behaviour।
Consistency Beats Cleverness
Avoid one endpoint using:
/orders/{id}/cancel
another using:
/products?action=disable
another using:
POST /updateInventory
without a reason।
Use a coherent design vocabulary across the API।
HTTP Does Not Eliminate Business Documentation
Even a conventional endpoint:
POST /orders
does not tell clients:
same Product cannot appear twice
Inventory is consumed at successful creation
current server-side Product price is used
initial Order state is UNPAID
These business rules belong in API documentation/OpenAPI and product requirements।
HTTP semantics provide structure, not the complete contract।
Example: Create Order Contract
Conceptually:
POST /orders
Request:
{
"items": [
{
"productId": "P-100",
"quantity": 2
}
]
}
Server decides:
CustomerId
→ authenticated caller
unit price
→ current Product price
status
→ UNPAID
total
→ derived
On success:
201 Created
This is a much stronger contract than generic:
POST /createOrder
returning 200 for everything।
Example: Browse Products
Conceptually:
GET /products
This is a read request।
Business definition of visible/orderable Products may involve:
Product active
AND
Inventory > 0
But the client does not need to know how we combine repositories internally।
The response presents the resulting resource representation।
Example: Own Order History
Conceptually:
GET /orders
for an authenticated customer might mean:
return my Orders
because ownership comes from authentication context।
We do not necessarily need:
GET /customers/{customerId}/orders
if the customer is not allowed to query arbitrary customer IDs।
This prevents client-controlled ownership scope।
Admin and Customer APIs May Share Resources
Admin may have broader access to:
Orders
than customer।
That does not automatically require entirely different domain models।
Authorization rules determine which representations/queries the caller may access।
We will handle this in the Security module।
HTTP Error Messages Should Not Leak Internals
Bad response:
{
"error": "org.hibernate.exception.ConstraintViolationException ..."
}
Clients should not receive:
SQL
stack traces
internal class names
secret configuration
API error responses should express actionable client-facing meaning।
Never Use 200 With Error JSON for Everything
Weak contract:
200 OK
{
"success": false,
"error": "Order not found"
}
This forces every client to ignore HTTP semantics and inspect custom fields for basic outcome classification।
Prefer using appropriate status codes plus consistent response body where needed।
Status Code Alone Is Also Not Enough
For meaningful business errors, client may need details such as:
error code
message
field errors
request correlation reference
Therefore good APIs use:
HTTP status
+
structured error representation
rather than choosing only one।
Domain Error to HTTP Mapping
Conceptually:
Invalid request DTO
↓
400
Order not found
↓
404
Authenticated user lacks permission
↓
403
Order lifecycle does not permit operation
↓
appropriate 4xx conflict/business response
unexpected database failure
↓
5xx
Exact mapping comes later।
HTTP Semantics Should Match UseCase Semantics
Our internal operation:
CreateOrderUseCase
naturally maps to:
POST /orders
Internal:
GetOrderHistoryUseCase
naturally maps to a GET collection request।
Internal:
CancelOrderUseCase
maps to an explicit mutating operation।
Internal:
PayOrderUseCase
maps to a mutating payment command, not a client-controlled status update।
This alignment makes the system easier to understand end-to-end।
A Useful Design Sequence
When designing an endpoint, don't start with:
Which annotation should I use?
Instead ask:
1. What business operation/resource is involved?
2. Is this read or mutation?
3. What does the client control?
4. What does the server control?
5. Is the operation naturally idempotent?
6. What resource URI best represents it?
7. What success result should be observable?
8. What expected failures exist?
9. Which HTTP semantics represent those outcomes?
Then implement the Handler।
Spring Annotation Comes Last
Only after the contract is understood do we choose something like:
@GetMapping(...)
or:
@PostMapping(...)
The annotation is implementation syntax।
The API behaviour is the design।
Common Mistake 1 — Every Endpoint Uses POST
HTTP loses semantic value।
Common Mistake 2 — Mutation Through GET
Safe-read assumptions are violated।
Common Mistake 3 — Client Controls Server-Owned State
Examples:
Order status
Order total
Customer ownership
purchase-time price
Common Mistake 4 — Every Failure Returns 500
Expected business/client failures are confused with unexpected server failure।
Common Mistake 5 — Every Success Returns 200
Creation/update/no-body semantics become less expressive।
Common Mistake 6 — Endpoint Names Mirror Java Methods
/getAllProducts and /createOrderUseCase expose implementation thinking rather than resource design।
Common Mistake 7 — DELETE Used for Product Deactivation Without Thinking
Business behaviour is deactivation, not physical deletion।
Common Mistake 8 — Assuming POST Retry Is Safe
Network ambiguity can cause duplicate effects।
Common Mistake 9 — Assuming Domain Validation Solves Request Retry
Order invariants do not automatically prevent duplicate Order creation or double external payment execution।
Common Mistake 10 — Designing API From Database Tables
Public behaviour should start from business use cases/resources, not CRUD generated from schema।
HTTP Review Checklist
Before approving an endpoint, ask:
Does the method match the operation semantics?
Is GET free from requested business mutation?
Is the resource URI meaningful and consistent?
Does the client control only legitimate input?
Are server-owned values derived on the server?
Is retry behaviour understood?
Is the operation naturally idempotent?
Does success use an appropriate status?
Are expected failures distinguishable?
Could the endpoint leak another customer's resource existence?
Are transport concerns remaining at the Handler boundary?
How HTTP Fits Our Architecture
Complete direction:
HTTP Method + URI + JSON
↓
Handler
↓
UseCase
↓
Domain
↓
Repository / External Service
Then:
Application result / failure
↓
Handler
↓
HTTP status + JSON
HTTP is the external contract।
It does not replace our domain or application architecture।
Our API Design Principles
For this project, we will follow these principles:
Use resource-oriented URIs.
Use HTTP methods according to their intended semantics.
Do not mutate business state through GET.
Keep authenticated ownership server-controlled.
Keep prices, totals, and lifecycle state server-controlled.
Use explicit business operations when generic field mutation would weaken meaning.
Use meaningful success and error status codes.
Keep request/response DTOs separate from domain behaviour.
Treat retry and idempotency as real system concerns.
Prefer conventional, predictable APIs over clever endpoints.
These principles will guide the rest of Module 5।
Engineering Principle
The core principle:
HTTP is not merely a transport pipe; its methods, status codes, and resource semantics form part of the application's public behavioural contract.
Another:
Clients should express intent through the API, while the server remains authoritative over business state and valid transitions.
And:
Design the operation first, choose the HTTP representation second, and write the Spring annotation last.
Summary
In this lesson, we learned that:
- HTTP is an external application contract, not just a set of controller annotations.
- Requests combine methods, URIs, headers, and optional bodies to communicate intent.
- GET should represent read behaviour and should not perform requested business mutations.
- Safe methods and idempotent operations are different concepts.
- Idempotency describes the intended final effect of repeated requests.
- POST is appropriate for many resource-creation and command-style operations, but is not a generic replacement for every method.
- PUT generally models full replacement at a known resource and has idempotent semantics.
- PATCH represents partial modification, while actual idempotency depends on the operation.
- DELETE should not be selected blindly when the business operation is actually deactivation.
- Resource-oriented URIs are preferable to exposing Java method/class names.
- Path parameters identify specific resources, while query parameters commonly support collection filtering/pagination.
- Authentication metadata belongs in HTTP/security infrastructure and becomes application-friendly identity before reaching UseCases.
- Success statuses such as
200,201, and204communicate different outcomes. 401and403represent different authentication/authorization situations.404can also be a deliberate choice for hiding existence of another customer's protected resource.- Business-state conflicts should not automatically become
500. - Expected domain/application errors should be translated into client-facing HTTP semantics.
- Client-controlled request data must not determine Order ownership, status, authoritative price, or total.
- Explicit business actions such as cancellation and payment may deserve explicit mutating endpoints rather than generic status mutation.
- HTTP method choice does not by itself make implementation safe or idempotent.
- Network timeouts create ambiguous outcomes, so retry-sensitive operations require deliberate design.
- Public API contracts should remain independent of Repository methods, Java class names, and internal architecture.
- Spring annotations should implement an already-designed HTTP contract rather than define the design accidentally.
Next lesson:
Designing Resources and Endpoints
There we will design the actual API surface for Products, Inventory, Orders, cancellation, and payment, decide which operations are resource reads versus business commands, and resolve the earlier Customer-endpoint ambiguity consistently with our accepted architecture: no local Customer resource solely for ordering.