Building REST APIs

HTTP Through a Backend Engineer's Eyes

ReadingPreview

আপনি একটি 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, and 204 communicate different outcomes.
  • 401 and 403 represent different authentication/authorization situations.
  • 404 can 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.