Start HereOrdered learning track

OpenAPI First API Governance

Learn Java Microservices CPQ OMS Platform - Part 005

OpenAPI First API Governance for a Java microservices CPQ and order management platform, covering API design rules, lifecycle compatibility, error contracts, idempotency, pagination, security boundaries, and CI governance.

18 min read3506 words
PrevNext
Lesson 0535 lesson track0106 Start Here
#java#microservices#cpq#oms+6 more

Part 005 — OpenAPI First API Governance

Part ini membahas bagaimana kita mendesain HTTP API untuk platform CPQ/OMS secara OpenAPI First: kontrak ditulis, direview, dilint, dites, dan dinegosiasikan sebelum implementasi service dianggap benar.

Tujuannya bukan sekadar punya file openapi.yaml. Tujuannya adalah membangun API governance system yang membuat service Java/JAX-RS/Jersey kita stabil, evolvable, mudah dites, dan aman untuk dipakai banyak consumer.

Dalam platform CPQ/OMS, API bukan hanya endpoint CRUD. API adalah batas kontrak untuk keputusan bisnis: validasi konfigurasi, kalkulasi harga, approval quote, submit order, cancel order, amend order, dan query status. Kesalahan kecil pada API design dapat menghasilkan quote yang salah, order ganda, audit trail lemah, atau integrasi enterprise yang rapuh.

1. Baseline Mental Model

OpenAPI Specification mendefinisikan format standar, language-agnostic, untuk mendeskripsikan HTTP API sehingga manusia dan mesin dapat memahami capability sebuah service tanpa membaca source code atau network traffic.

Dalam seri ini, OpenAPI dipakai sebagai source of truth untuk HTTP interface, bukan dokumentasi sekunder.

Artinya:

  1. API contract dibuat sebelum resource Jersey final.
  2. Contract direview seperti code.
  3. Generated code boleh dipakai, tetapi domain logic tidak boleh bocor ke generated layer.
  4. Compatibility rules wajib eksplisit.
  5. API tests diturunkan dari contract.
  6. Breaking change tidak boleh masuk tanpa versioning/migration plan.

2. Why OpenAPI First Matters in CPQ/OMS

CPQ/OMS API memiliki karakteristik yang lebih berat daripada API produk internal sederhana:

AreaRisiko Jika API Tidak Digovern
Quote creationQuote dibuat tanpa commercial snapshot yang cukup.
Configuration validationConsumer tidak tahu kenapa konfigurasi invalid.
PricingRounding, currency, discount stacking, dan effective date tidak eksplisit.
ApprovalAction tidak idempotent, escalation sulit diaudit.
Order submissionRetry dari client dapat membuat duplicate order.
Order statusState tidak konsisten antara order header dan order line.
IntegrationPartner/BSS/CRM memakai semantic yang berbeda.
AuditTidak ada stable request identity, actor identity, dan decision trail.

OpenAPI First membuat kita memaksa pertanyaan desain sebelum implementation bias mengambil alih.

Contoh pertanyaan yang harus dijawab sebelum coding:

  • Apakah submitQuote idempotent?
  • Apakah quoteId dibuat oleh server atau client?
  • Apakah harga dihitung ulang saat quote diterima?
  • Apakah client boleh mengirim discount manual?
  • Apa bentuk error saat product incompatible?
  • Bagaimana API menunjukkan approval sedang menunggu siapa?
  • Apakah cancel order berlaku untuk seluruh order atau line tertentu?
  • Apa response saat request yang sama dikirim ulang?

3. Kaufman Deconstruction: Skill yang Sebenarnya Dipelajari

Skill besar part ini adalah: mendesain API enterprise yang bisa berevolusi tanpa menghancurkan consumer dan tanpa melemahkan invariant bisnis.

Kita pecah menjadi sub-skill:

Sub-skillPraktik UtamaOutput
Capability framingMenentukan use case, actor, command/query boundaryAPI capability map
Resource modelingMenentukan resource, action, lifecycle, identityEndpoint map
Schema modelingRequest/response DTO, validation, enum, money, timeComponent schemas
Error designError taxonomy, field errors, business errorsStandard error model
IdempotencyIdempotency key, replay response, duplicate semanticsIdempotent command policy
CompatibilityBackward-compatible change rulesAPI evolution policy
Governance automationLint, diff, contract test, generated codeCI quality gate

Target part ini: setelah selesai, kita tidak hanya bisa menulis OpenAPI spec, tetapi bisa menjelaskan kenapa suatu API aman atau tidak aman secara lifecycle.

4. API Taxonomy untuk CPQ/OMS

Jangan mulai dari endpoint. Mulai dari taxonomy capability.

4.1 Command APIs

Command API mengubah state. Di CPQ/OMS, command API harus hampir selalu memiliki:

  • stable request identity,
  • actor identity,
  • tenant context,
  • idempotency behavior,
  • validation result,
  • audit effect,
  • deterministic response shape.

Contoh command:

POST /v1/quotes
POST /v1/quotes/{quoteId}/submit
POST /v1/quotes/{quoteId}/approve
POST /v1/quotes/{quoteId}/accept
POST /v1/orders
POST /v1/orders/{orderId}/cancel

4.2 Query APIs

Query API membaca state. Tantangan query API bukan hanya pagination, tetapi juga projection correctness.

Contoh:

GET /v1/quotes/{quoteId}
GET /v1/quotes?customerId=...&status=...
GET /v1/orders/{orderId}
GET /v1/orders/{orderId}/timeline

Query API boleh memakai read model yang optimized, tetapi response harus jelas apakah datanya strongly consistent atau eventually consistent.

4.3 Decision APIs

Decision API tidak selalu menyimpan aggregate final, tetapi menghasilkan keputusan penting.

Contoh:

POST /v1/configurations/validate
POST /v1/prices/calculate
POST /v1/approval-policies/evaluate

Decision API wajib explainable. Jika konfigurasi invalid, client harus mendapat alasan yang bisa dipakai UI dan audit.

4.4 Callback APIs

Callback APIs menerima sinyal dari sistem eksternal. Ini rawan duplicate, out-of-order, dan untrusted payload.

Contoh:

POST /v1/integration/provisioning/callbacks
POST /v1/integration/payment/callbacks

Callback API wajib:

  • authenticate source,
  • verify signature bila tersedia,
  • deduplicate external event ID,
  • tolerate retry,
  • map external state ke internal transition secara eksplisit.

4.5 Operational APIs

Operational API bukan API bisnis. Jangan campur dengan public business API.

Contoh:

GET /health/live
GET /health/ready
GET /internal/diagnostics/orders/{orderId}

Operational API harus dibatasi network/security policy. Jangan membuka endpoint repair tanpa authorization kuat.

5. Resource Design: Noun, Command, atau Workflow Action?

Rule praktis:

  1. Gunakan noun resource untuk lifecycle entity utama.
  2. Gunakan sub-resource untuk collection atau timeline.
  3. Gunakan action endpoint ketika business action tidak natural sebagai CRUD.
  4. Hindari memaksa semua hal menjadi PUT hanya demi terlihat RESTful.

Dalam CPQ/OMS, action endpoint sering lebih jujur:

POST /v1/quotes/{quoteId}/submit
POST /v1/quotes/{quoteId}/accept
POST /v1/orders/{orderId}/cancel
POST /v1/orders/{orderId}/resume

Ini lebih jelas daripada:

PATCH /v1/quotes/{quoteId}
{ "status": "SUBMITTED" }

Mengubah status secara langsung melemahkan invariant karena status transition bukan sekadar update field. Ia adalah command dengan rules, authorization, audit, dan side effect.

6. Endpoint Naming Standard

Gunakan standard naming berikut di series ini:

ConcernStandard
Version prefix/v1
Resource nameplural noun: /quotes, /orders
ID path param{quoteId}, {orderId}
Actionverb sub-resource: /submit, /accept, /cancel
SearchGET /resources?... untuk simple search; POST /resources/search untuk complex criteria
Timeline/resources/{id}/timeline
Internal endpoint/internal/...
Health endpoint/health/live, /health/ready

6.1 Do

POST /v1/quotes/{quoteId}/submit
POST /v1/orders/{orderId}/cancel
GET /v1/orders/{orderId}/timeline

6.2 Avoid

POST /v1/submitQuote
POST /v1/orderCancellation
PATCH /v1/orders/{orderId}/status
GET /v1/getQuoteById?id=...

7. OpenAPI Document Structure

Untuk service besar, satu file openapi.yaml akan cepat sulit dirawat. Gunakan modular layout.

services/
  quote-service/
    api/
      openapi.yaml
      paths/
        quotes.yaml
        quote-actions.yaml
      components/
        schemas/
          quote.yaml
          quote-line.yaml
          money.yaml
          error.yaml
        parameters/
          pagination.yaml
        responses/
          errors.yaml
        security/
          bearer-auth.yaml

Root document tetap mudah dibaca:

openapi: 3.1.1
info:
  title: Quote Service API
  version: 1.0.0
  description: API contract for managing CPQ quote lifecycle.
servers:
  - url: https://api.example.com/quote-service
paths:
  /v1/quotes:
    $ref: './paths/quotes.yaml'
  /v1/quotes/{quoteId}/submit:
    $ref: './paths/quote-actions.yaml#/submitQuote'
components:
  securitySchemes:
    bearerAuth:
      $ref: './components/security/bearer-auth.yaml'

The key is not modularity itself. The key is to make ownership visible:

  • path owner,
  • schema owner,
  • review owner,
  • compatibility reviewer,
  • generated-code consumer.

8. Standard Metadata

Every OpenAPI document must define:

openapi: 3.1.1
info:
  title: Quote Service API
  version: 1.0.0
  description: >
    Contract for quote lifecycle management in the CPQ/OMS platform.
  contact:
    name: CPQ Platform Team
    email: cpq-platform@example.com
  license:
    name: Internal Use Only
servers:
  - url: https://api.example.com/quote-service
    description: Production
  - url: https://staging-api.example.com/quote-service
    description: Staging

Use info.version for API contract release. Do not confuse it with service build version.

Version TypeExampleMeaning
API contract version1.3.0Contract compatibility version
Service artifact versionquote-service-2026.07.02.12Runtime deployable version
Business policy versionpricing-policy-2026-Q3Effective business rules
Event schema versionQuoteSubmitted.v2Async contract version

9. OperationId Governance

operationId is not decorative. It affects generated clients, test naming, docs, and traceability.

Use deterministic operation IDs:

operationId: createQuote
operationId: submitQuote
operationId: getQuoteById
operationId: searchQuotes
operationId: cancelOrder

Avoid:

operationId: quotePost
operationId: doSubmit
operationId: handleAction
operationId: orderController_cancel

Rules:

  1. Must be unique across the service.
  2. Must be stable across compatible changes.
  3. Must express business capability.
  4. Must not include framework/controller names.
  5. Must not change unless the operation is removed or intentionally replaced.

10. Request Identity and Correlation Headers

In CPQ/OMS, request headers are part of the governance model.

Minimum standard headers:

HeaderRequiredPurpose
X-Correlation-Idrecommended/requestedTrace a business flow across services.
X-Request-Idyes for commandsUnique request attempt identity.
Idempotency-Keyyes for unsafe commandsDeduplicate client retries.
X-Tenant-Iddepends on auth modelExplicit tenant context if not fully derived from token.
X-Actor-Idno if token-derivedHuman/system actor context.

Example parameter component:

components:
  parameters:
    CorrelationId:
      name: X-Correlation-Id
      in: header
      required: false
      schema:
        type: string
        minLength: 8
        maxLength: 128
      description: Correlation identifier propagated across services.
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 16
        maxLength: 128
      description: Stable key used to deduplicate retry of unsafe commands.

10.1 Correlation ID vs Request ID vs Idempotency Key

These are not the same.

IdentifierScopeStable Across Retry?Used For
Correlation IDBusiness flowyesdistributed tracing, logs
Request IDindividual HTTP attemptnorequest diagnostics
Idempotency keybusiness command attemptyesduplicate prevention

Example:

A browser times out while submitting an order. It retries.

  • Same Idempotency-Key
  • Same or related X-Correlation-Id
  • Different X-Request-Id

If you mix these concepts, observability and correctness both degrade.

11. Idempotency Governance

Every unsafe command must declare idempotency behavior.

Unsafe command means:

  • creates aggregate,
  • changes state,
  • triggers process,
  • publishes event,
  • calls external dependency,
  • starts Camunda process,
  • creates order/provisioning side effect.

For commands like POST /v1/orders, require Idempotency-Key.

/v1/orders:
  post:
    operationId: submitOrder
    summary: Submit an accepted quote as an order.
    parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SubmitOrderRequest'
    responses:
      '201':
        description: Order created.
      '200':
        description: Existing result for an idempotent retry.
      '409':
        description: Same idempotency key used with a different request fingerprint.

11.2 Idempotency Storage Model

At implementation level:

idempotency_record
- tenant_id
- idempotency_key
- request_fingerprint
- command_type
- aggregate_id
- response_status
- response_body_snapshot
- expires_at
- created_at

Rules:

  1. Same key + same request fingerprint returns previous result.
  2. Same key + different request fingerprint returns 409 Conflict.
  3. Key TTL must exceed realistic client retry window.
  4. For high-risk commands, persist response snapshot.
  5. Idempotency record creation must be transactionally aligned with aggregate creation.

12. Standard Error Contract

Do not let every service invent its own error response.

Use one standard error envelope:

ErrorResponse:
  type: object
  required:
    - errorId
    - code
    - message
    - category
    - retryable
    - timestamp
  properties:
    errorId:
      type: string
      description: Unique error instance identifier for support and logs.
    code:
      type: string
      description: Stable machine-readable error code.
      examples: [QUOTE_NOT_FOUND, CONFIGURATION_INVALID]
    message:
      type: string
      description: Human-readable summary safe for clients.
    category:
      type: string
      enum:
        - VALIDATION
        - BUSINESS_RULE
        - AUTHORIZATION
        - CONFLICT
        - RATE_LIMIT
        - DEPENDENCY
        - INTERNAL
    retryable:
      type: boolean
    fieldErrors:
      type: array
      items:
        $ref: '#/components/schemas/FieldError'
    details:
      type: object
      additionalProperties: true
    timestamp:
      type: string
      format: date-time

Field error:

FieldError:
  type: object
  required:
    - field
    - code
    - message
  properties:
    field:
      type: string
      examples: ["lineItems[0].quantity"]
    code:
      type: string
      examples: [MINIMUM_VALUE, REQUIRED, INCOMPATIBLE_PRODUCT]
    message:
      type: string

12.1 Error Code Rules

Error codes must be:

  • stable,
  • uppercase snake case,
  • documented,
  • not coupled to Java exception class names,
  • safe to show to clients,
  • testable in contract tests.

Good:

QUOTE_NOT_FOUND
QUOTE_NOT_EDITABLE
CONFIGURATION_INVALID
PRICE_EXPIRED
ORDER_ALREADY_SUBMITTED
IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD

Bad:

NullPointerException
IllegalStateException
DB_ERROR_1002
SomethingWentWrong
ValidationFailed

13. HTTP Status Code Policy

Status code alone is not enough, but it still matters.

StatusUse
200 OKSuccessful read or idempotent replay result.
201 CreatedNew resource created.
202 AcceptedCommand accepted asynchronously.
204 No ContentSuccessful command with no body. Use sparingly.
400 Bad RequestSyntactically invalid or structurally invalid request.
401 UnauthorizedMissing/invalid authentication.
403 ForbiddenAuthenticated but not allowed.
404 Not FoundResource not found or intentionally hidden.
409 ConflictState conflict, concurrency conflict, idempotency conflict.
422 Unprocessable EntityValid JSON but invalid business semantics.
429 Too Many RequestsRate limited.
500 Internal Server ErrorUnexpected server failure.
502/503/504Dependency or availability failure.

13.1 Business Validation: 400 or 422?

Use this rule:

  • 400: request cannot be parsed or violates generic schema constraints.
  • 422: request is structurally valid but violates business rules.

Example:

Request ProblemStatus
malformed JSON400
missing required customerId400
quantity is string instead of integer400
product is incompatible with selected bundle422
discount exceeds approval threshold422 or 202 depending flow
quote is already accepted409

14. Pagination, Sorting, and Filtering

Search APIs must be consistent.

14.1 Offset Pagination

Offset pagination is simple but unstable for fast-changing data.

GET /v1/quotes?page=0&pageSize=50&sort=createdAt:desc

Suitable for admin screens with modest data movement.

14.2 Cursor Pagination

Cursor pagination is better for high-volume, time-ordered data.

GET /v1/orders?limit=50&cursor=eyJjcmVhdGVkQXQiOiIyMDI2LTA3LTAy..."

Recommended for event-like/timeline query.

14.3 Standard Search Response

PagedResponse:
  type: object
  required:
    - items
    - pageInfo
  properties:
    items:
      type: array
      items: {}
    pageInfo:
      $ref: '#/components/schemas/PageInfo'

PageInfo:
  type: object
  required:
    - limit
    - hasNext
  properties:
    limit:
      type: integer
      minimum: 1
      maximum: 500
    hasNext:
      type: boolean
    nextCursor:
      type: string
      nullable: true

In OpenAPI 3.1, prefer JSON Schema union style over older nullable patterns where toolchain supports it. If generator support is inconsistent, define a platform convention and enforce it.

15. Money, Currency, and Precision

Never model money as floating point.

Bad:

amount:
  type: number
  format: double

Better:

Money:
  type: object
  required:
    - amount
    - currency
  properties:
    amount:
      type: string
      pattern: '^-?\\d+(\\.\\d{1,6})?$'
      description: Decimal amount encoded as string to avoid floating point loss.
    currency:
      type: string
      minLength: 3
      maxLength: 3
      examples: [USD, IDR, EUR]

Why string?

Because JSON number does not preserve decimal scale semantics across all runtimes. Java can map to BigDecimal, but JavaScript clients may lose precision or scale intent.

15.1 Monetary Governance Rules

  1. Currency is always required.
  2. Amount is serialized as decimal string.
  3. Tax-inclusive vs tax-exclusive must be explicit.
  4. Recurring vs one-time charge must be explicit.
  5. Rounding mode must be decided by pricing engine, not client.
  6. Quote stores price snapshot, not only price reference.

16. Time and Effective Dating

Use date-time for instants, but be explicit about semantic meaning.

createdAt:
  type: string
  format: date-time
  description: UTC instant when the quote was created.

priceEffectiveAt:
  type: string
  format: date-time
  description: Business instant used to select price book and promotion rules.

validUntil:
  type: string
  format: date-time
  description: UTC instant after which the quote can no longer be accepted.

Avoid ambiguous fields:

createdDate: string
expiry: string
time: string

16.1 Time Governance Rules

  1. Store instants in UTC.
  2. Include timezone/offset in API timestamp.
  3. Use date only for date-only business concepts.
  4. Do not let client decide server-side effective date unless explicitly authorized.
  5. Price validity, quote expiry, and SLA timers must be independently modeled.

17. Enum Evolution

Enums are dangerous in distributed systems because consumers often assume exhaustive values.

Example:

QuoteStatus:
  type: string
  enum:
    - DRAFT
    - SUBMITTED
    - APPROVAL_PENDING
    - APPROVED
    - ACCEPTED
    - EXPIRED
    - CANCELLED

Governance rules:

  1. Adding enum value can be breaking for strict clients.
  2. Use documentation to tell clients to tolerate unknown values.
  3. For external APIs, consider x-extensible-enum convention if supported by tooling.
  4. Avoid exposing internal micro-states if they are not stable.
  5. Separate public status from internal process state.

Good separation:

OrderStatus:
  type: string
  enum: [RECEIVED, IN_PROGRESS, PARTIALLY_COMPLETED, COMPLETED, CANCELLED, FAILED]

OrderLineStatus:
  type: string
  enum: [PENDING, IN_PROGRESS, COMPLETED, CANCELLED, FAILED, MANUAL_REVIEW]

Do not expose Camunda activity IDs as public order status.

18. Versioning Strategy

API versioning is a compatibility strategy, not a URL decoration.

Use major version in URL:

/v1/quotes
/v2/quotes

Use semantic version in OpenAPI info.version:

info:
  version: 1.4.2

18.1 Compatible Changes

Usually compatible:

  • adding optional response field,
  • adding optional request field with default behavior,
  • adding new endpoint,
  • adding new error detail field,
  • adding pagination metadata field,
  • relaxing validation rule.

Potentially breaking:

  • adding required request field,
  • removing field,
  • renaming field,
  • changing field type,
  • changing enum semantics,
  • changing default sort order,
  • changing idempotency behavior,
  • changing error code,
  • changing monetary precision,
  • returning async 202 where client expects sync 201.

18.2 Change Classification Table

ChangeClassificationRequired Action
Add optional quoteNameminorContract diff + docs
Add required salesChannelbreakingNew API version or compatibility default
Rename lineItems to itemsbreakingNew API version
Add new OrderStatusriskyConsumer compatibility review
Change amount from number to stringbreakingNew API version
Add Retry-After header to 429compatibleDocs update
Change 409 to 422 for same conditionbreaking-ishConsumer review

19. Security Contract

OpenAPI must describe authentication and authorization expectations enough for consumers and tests.

Example:

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
security:
  - bearerAuth: []

But authentication scheme is not sufficient. For sensitive commands, document authorization behavior.

/v1/quotes/{quoteId}/approve:
  post:
    operationId: approveQuote
    summary: Approve a quote pending approval.
    description: >
      Requires quote approval permission for the tenant and approval policy level.
      The actor must be eligible for the current approval task.

19.1 Security Governance Rules

  1. Never trust tenant ID only from request body.
  2. Prefer tenant from token claims or gateway context.
  3. Do not expose internal IDs that bypass tenant scope.
  4. Avoid endpoints that allow arbitrary status mutation.
  5. Separate operator repair APIs from business APIs.
  6. Audit every privileged command.

20. JAX-RS/Jersey Adapter Boundary

OpenAPI First should not lead to domain logic living inside generated DTOs or Jersey resource classes.

Use this boundary:

Rules:

  1. Generated DTOs are transport models.
  2. Domain models are handwritten.
  3. Resource methods are thin.
  4. Validation occurs at multiple layers:
    • schema validation,
    • bean validation,
    • application validation,
    • domain invariant enforcement.
  5. Exception mapper translates domain/application errors to standard error contract.

Example resource shape:

@Path("/v1/quotes")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class QuoteResource {

    private final QuoteApplicationService quoteService;
    private final QuoteApiMapper mapper;

    @POST
    public Response createQuote(
            @HeaderParam("Idempotency-Key") String idempotencyKey,
            @HeaderParam("X-Correlation-Id") String correlationId,
            CreateQuoteRequest request) {

        CreateQuoteCommand command = mapper.toCommand(
                request,
                idempotencyKey,
                correlationId
        );

        QuoteResult result = quoteService.createQuote(command);
        return Response.status(Response.Status.CREATED)
                .entity(mapper.toResponse(result))
                .build();
    }
}

The resource is not where pricing, approval, or order rules live.

21. API Linting Rules

A mature platform enforces API quality automatically.

Minimum lint rules:

RuleReason
Every operation has operationIdGenerated client stability
Every operation has summary/descriptionDocumentation quality
Every command declares idempotency behaviorRetry safety
No inline complex schemasReuse and reviewability
Error responses use standard error modelCross-service consistency
No undocumented 5xx only responseConsumer clarity
Path parameters use lower camel ID namesConsistency
Date-time fields have semantic descriptionAvoid ambiguous time semantics
Money uses standard Money schemaPrecision safety
Security scheme declaredAccess control visibility
No raw object without documented additional propertiesAvoid contract holes

Example CI step:

spectral lint services/quote-service/api/openapi.yaml
openapi-diff previous.yaml current.yaml --fail-on-incompatible
mvn -pl services/quote-service test

The exact tool can vary. The invariant is: API contract must fail CI before broken implementation can merge.

22. Contract Diff Governance

Every API change should be classified.

A diff tool cannot understand all semantic breakage. Human review is still required for:

  • enum semantics,
  • field meaning,
  • default value behavior,
  • monetary precision,
  • idempotency semantics,
  • state transition behavior,
  • authorization behavior.

23. Example: Quote API Contract Slice

openapi: 3.1.1
info:
  title: Quote Service API
  version: 1.0.0
paths:
  /v1/quotes:
    post:
      operationId: createQuote
      summary: Create a new draft quote.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/CorrelationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateQuoteRequest'
      responses:
        '201':
          description: Quote created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuoteResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/BusinessValidationError'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 16
        maxLength: 128
    CorrelationId:
      name: X-Correlation-Id
      in: header
      required: false
      schema:
        type: string
        minLength: 8
        maxLength: 128
  schemas:
    CreateQuoteRequest:
      type: object
      required:
        - customerId
        - salesChannel
        - lineItems
      properties:
        customerId:
          type: string
        salesChannel:
          type: string
          enum: [DIRECT, PARTNER, ONLINE, CALL_CENTER]
        lineItems:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/CreateQuoteLineRequest'
    CreateQuoteLineRequest:
      type: object
      required:
        - productCode
        - quantity
      properties:
        productCode:
          type: string
        quantity:
          type: integer
          minimum: 1

This is intentionally not complete. In real service, we would extract schemas and define standard error components.

24. Example: Submit Quote Contract

Submitting a quote is a state transition, not a field update.

/v1/quotes/{quoteId}/submit:
  post:
    operationId: submitQuote
    summary: Submit a draft quote for validation, pricing, and approval evaluation.
    parameters:
      - name: quoteId
        in: path
        required: true
        schema:
          type: string
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/CorrelationId'
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SubmitQuoteRequest'
    responses:
      '200':
        description: Quote submitted or previous idempotent result returned.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitQuoteResponse'
      '409':
        description: Quote is not in a submittable state or idempotency conflict.
      '422':
        description: Quote cannot be submitted due to business validation failure.

The semantics must define:

  • required source status: DRAFT,
  • possible target status: SUBMITTED, APPROVAL_PENDING, APPROVED,
  • whether price is recalculated,
  • whether approval policy is evaluated synchronously,
  • whether event is published,
  • whether idempotent retry returns same result.

25. API Review Checklist

Use this before implementing any endpoint.

25.1 Business Semantics

  • Does the endpoint represent a real business capability?
  • Is it command, query, decision, callback, or operational API?
  • What aggregate owns the change?
  • What state transition happens?
  • What invariant can be violated?
  • What audit record is required?

25.2 Contract Shape

  • Are request and response schemas explicit?
  • Are all required fields justified?
  • Are IDs stable and scoped?
  • Are enums safe to evolve?
  • Are monetary and temporal fields modeled precisely?
  • Is error response standard?

25.3 Distributed Behavior

  • Is the command idempotent?
  • What happens on retry after timeout?
  • Can response be replayed?
  • Does command publish event?
  • Is eventual consistency visible?
  • Does it start or interact with Camunda process?

25.4 Consumer Compatibility

  • Would existing clients break?
  • Is field removal avoided?
  • Is enum addition safe?
  • Is default behavior unchanged?
  • Is error code stable?
  • Is migration path documented?

25.5 Operations

  • Does endpoint expose correlation ID?
  • Are metrics and logs identifiable by operation ID?
  • Are rate limits needed?
  • Are dependency failures classified?
  • Is authorization clear?
  • Are repair/runbook implications understood?

26. Anti-Patterns

26.1 CRUD API Over Workflow Domain

Bad:

PATCH /v1/orders/{orderId}
{ "status": "COMPLETED" }

Why bad:

  • bypasses lifecycle rules,
  • hides actor intent,
  • weak audit,
  • hard to validate side effects,
  • can conflict with Camunda process state.

Better:

POST /v1/orders/{orderId}/complete-line
POST /v1/orders/{orderId}/cancel
POST /v1/orders/{orderId}/manual-repair

26.2 Generated DTO as Domain Model

Bad:

public void submitQuote(QuoteResponse quoteResponse) { ... }

Better:

public SubmitQuoteResult submitQuote(SubmitQuoteCommand command) { ... }

26.3 Silent Optional Field Semantics

Bad:

discount:
  type: number

Questions left unanswered:

  • Is null different from zero?
  • Is it percentage or amount?
  • Who is allowed to set it?
  • Does it require approval?
  • Does it stack with promotion?

Better:

ManualDiscountRequest:
  type: object
  required:
    - discountType
    - value
    - reasonCode
  properties:
    discountType:
      type: string
      enum: [PERCENTAGE, FIXED_AMOUNT]
    value:
      type: string
      pattern: '^\\d+(\\.\\d{1,6})?$'
    reasonCode:
      type: string

26.4 Error as Free Text

Bad:

{
  "error": "Quote invalid"
}

Better:

{
  "errorId": "err-01J...",
  "code": "CONFIGURATION_INVALID",
  "message": "The quote contains invalid product configuration.",
  "category": "BUSINESS_RULE",
  "retryable": false,
  "fieldErrors": [
    {
      "field": "lineItems[1].productCode",
      "code": "INCOMPATIBLE_PRODUCT",
      "message": "Product ADDON_BACKUP requires BASE_STORAGE."
    }
  ],
  "timestamp": "2026-07-02T10:15:30Z"
}

27. Practice Lab

Build an API governance slice for quote-service.

27.1 Task

Create OpenAPI definitions for:

  1. POST /v1/quotes
  2. GET /v1/quotes/{quoteId}
  3. POST /v1/quotes/{quoteId}/submit
  4. POST /v1/quotes/{quoteId}/accept
  5. GET /v1/quotes/{quoteId}/timeline

27.2 Required Artifacts

services/quote-service/api/openapi.yaml
services/quote-service/api/components/schemas/money.yaml
services/quote-service/api/components/schemas/error.yaml
services/quote-service/api/components/schemas/quote.yaml
services/quote-service/api/paths/quotes.yaml
services/quote-service/api/paths/quote-actions.yaml

27.3 Acceptance Criteria

  • All command endpoints require Idempotency-Key.
  • All operations have stable operationId.
  • All errors use ErrorResponse.
  • Quote monetary values use Money schema.
  • Quote status is not directly patchable.
  • Submit and accept are modeled as commands.
  • 409 and 422 are both used intentionally.
  • API diff can be run in CI.

28. Production Readiness Signals

An API is production-ready when:

  1. Contract can be consumed without reading server code.
  2. Error codes are stable enough for UI/partner handling.
  3. Idempotent retry behavior is deterministic.
  4. Contract tests fail when implementation drifts.
  5. Breaking changes are detected before merge.
  6. Observability uses operation ID and correlation ID.
  7. Authorization behavior is explicit.
  8. Business invariants are visible in the API design.
  9. Consumer teams know what changes are safe.
  10. Operations team can debug a failed request using error ID and correlation ID.

29. Common Interview/Architecture Review Questions

29.1 Why not just generate OpenAPI from code?

Code-first generation is useful for documentation, but it usually discovers the API after implementation. For CPQ/OMS, that is too late. We need review before code because the API expresses business lifecycle, compatibility, and integration semantics.

29.2 Is OpenAPI enough for distributed contract safety?

No. OpenAPI covers HTTP contract shape, not all business semantics. We still need:

  • contract tests,
  • schema validation,
  • API diff,
  • consumer review,
  • business invariant tests,
  • event contract governance.

29.3 Should every service expose public APIs?

No. Some services should have internal APIs only. External API composition may live behind an API gateway or BFF. Do not leak internal decomposition to consumers without reason.

29.4 Should we expose Camunda process IDs?

Usually no. Process instance IDs are operational identifiers, not stable business identifiers. Public API should expose order/quote IDs and timeline/status projections.

30. Key Takeaways

  • OpenAPI First is an engineering control system, not documentation theater.
  • CPQ/OMS APIs must model lifecycle commands, not direct status mutation.
  • Idempotency is mandatory for unsafe commands.
  • Standard error contract is essential for UI, partner integration, and operations.
  • Money and time must be modeled precisely.
  • API compatibility requires both automated diffing and human semantic review.
  • Generated code must stay at the adapter boundary.
  • Public API state should not leak internal Camunda or database state.

31. Reference Baseline

  • OpenAPI Specification v3.1.1, official specification.
  • OpenAPI Initiative, formal standard for describing HTTP APIs.
  • JSON Schema Draft 2020-12, current JSON Schema baseline.
  • Jakarta REST/JAX-RS and Jersey will be used in later parts for implementation of the HTTP adapter layer.
Lesson Recap

You just completed lesson 05 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.