Series MapLesson 18 / 60
Build CoreOrdered learning track

Learn Enterprise Cpq Oms Glassfish Camunda8 Part 018 Tm Forum Inspired Api Mapping

19 min read3635 words
PrevNext
Lesson 1860 lesson track1233 Build Core

title: Build From Scratch: Enterprise Java Microservices CPQ & Order Management Platform - Part 018 description: Memetakan API CPQ/OMS enterprise dengan inspirasi TM Forum Open APIs: TMF620 Product Catalog, TMF648 Quote, TMF622 Product Ordering, TMF663 Shopping Cart, TMF637 Product Inventory, TMF641 Service Ordering, anti-corruption layer, canonical model, dan adaptation strategy. series: learn-enterprise-cpq-oms-glassfish-camunda8 seriesTitle: Build From Scratch: Enterprise Java Microservices CPQ & Order Management Platform order: 18 partTitle: TM Forum Inspired API Mapping tags:

  • java
  • microservices
  • cpq
  • oms
  • tm-forum
  • openapi
  • api-design
  • product-catalog
  • quote
  • product-ordering
  • anti-corruption-layer date: 2026-07-02

Part 018 — TM Forum Inspired API Mapping

Di Part 017 kita membahas API versioning dan compatibility.

Sekarang kita akan memetakan API CPQ/OMS kita terhadap vocabulary enterprise commerce yang lebih luas, terutama yang diinspirasi oleh TM Forum Open APIs.

Penting:

Kita tidak akan menyalin TM Forum mentah-mentah.

Kita akan memakainya sebagai bahasa referensi untuk menghindari desain domain yang terlalu lokal, terlalu CRUD, atau terlalu tergantung implementasi internal.

TM Forum berguna karena banyak konsep enterprise telco/commerce sudah diberi nama:

  • Product Catalog,
  • Product Offering,
  • Product Specification,
  • Product Offering Price,
  • Quote,
  • Product Order,
  • Product Inventory,
  • Service Catalog,
  • Service Ordering,
  • Service Inventory,
  • Party,
  • Account,
  • Agreement.

Tetapi platform kita tetap punya kebutuhan sendiri:

  • CPQ configuration engine,
  • pricing explainability,
  • approval policy,
  • quote-to-order conversion,
  • order decomposition,
  • fulfillment plan,
  • Camunda orchestration,
  • Kafka event model,
  • Redis acceleration,
  • operational repair,
  • audit defensibility.

Jadi pendekatannya:

Use TM Forum as vocabulary and integration boundary.
Use our own canonical model for internal correctness.

1. Kenapa Perlu TM Forum-Inspired Mapping?

Tanpa referensi eksternal, tim sering membuat istilah sendiri:

Package
Bundle
Product
Offering
Plan
SKU
Service
Subscription
Asset
Contract
Order Item
Line Item
Provisioning Item
Fulfillment Item

Lalu setiap tim memberi arti berbeda.

Di CPQ/OMS production, vocabulary mismatch menyebabkan:

  • quote item tidak bisa dikonversi ke order item,
  • product catalog tidak cocok dengan inventory,
  • order amendment membingungkan,
  • billing tidak tahu charge mana yang recurring,
  • provisioning tidak tahu service mana yang perlu dibuat,
  • support tidak bisa menelusuri installed base,
  • audit tidak bisa menjelaskan commercial promise.

TM Forum membantu memberi baseline vocabulary.

Tetapi jangan lupa:

External standard vocabulary is not a substitute for internal domain modelling.

2. API Keluarga TM Forum yang Relevan

Untuk seri ini, kita anggap keluarga API berikut sebagai sumber inspirasi utama.

TMF APIDomainRelevansi untuk Platform Kita
TMF620 Product Catalog ManagementCatalogProduct offering, product spec, product offering price, lifecycle
TMF663 Shopping CartPre-quote cartOptional jika platform punya cart sebelum quote
TMF648 Quote ManagementQuoteQuote, quote item, quote lifecycle, customer-facing commercial proposal
TMF622 Product OrderingProduct orderOrder capture, product order item, order state
TMF637 Product InventoryInstalled productAsset/subscription/installed base view
TMF633 Service CatalogTechnical service catalogMapping commercial offering ke service spec
TMF641 Service OrderingService orderDownstream technical fulfillment order
TMF638 Service InventoryService instanceRuntime service installed base
TMF632 Party ManagementPartyPerson/organization reference
TMF666 Account ManagementAccountBilling/customer account reference
TMF651 Agreement ManagementAgreementContract/agreement reference

Kita tidak perlu mengimplementasikan semuanya sekarang. Yang penting adalah tahu boundary-nya.


3. Vocabulary Alignment

Mapping awal:

Vocabulary TM ForumCanonical Model KitaCatatan
ProductSpecificationProductSpecificationTemplate teknis-komersial produk
ProductOfferingProductOfferingSesuatu yang bisa dijual ke customer
ProductOfferingPriceOfferingPrice / PriceComponentSumber harga katalog, bukan hasil pricing quote
QuoteQuoteCommercial proposal dengan snapshot
QuoteItemQuoteItemBaris quote, berisi offering + configuration + price snapshot
ProductOrderOrderExecution commitment
ProductOrderItemOrderItemBaris order hasil quote/direct order
ProductInstalledProduct / AssetProduk yang sudah aktif/terpasang
ServiceSpecificationTechnicalServiceSpecificationDefinisi service teknis
ServiceOrderTechnicalFulfillmentOrderOrder ke fulfillment/provisioning layer
ServiceServiceInstanceService runtime/aktif
RelatedPartyPartyReferenceReferensi customer/user/organization
BillingAccountBillingAccountReferenceReferensi account billing, bukan owner semua data
AgreementAgreementReferenceKontrak/terms reference

Dalam sistem kita, nama boleh tidak identik, tetapi mapping harus eksplisit.


4. Canonical Model Internal vs External API Model

Jangan jadikan model eksternal sebagai domain model internal.

Kenapa?

Karena external API model sering punya karakteristik:

  • generic,
  • extensible,
  • polymorphic,
  • memakai @type, @baseType, @schemaLocation,
  • relationship-heavy,
  • tidak selalu cukup presisi untuk invariant internal,
  • dirancang untuk interoperability, bukan internal enforcement.

Domain internal kita butuh:

  • invariant kuat,
  • state machine eksplisit,
  • aggregate boundary jelas,
  • command semantics eksplisit,
  • idempotency,
  • audit evidence,
  • pricing explanation,
  • configuration validation,
  • decomposition trace.

Jadi mapping layer wajib ada.


5. Jangan Cargo-Cult @type

Banyak implementasi TMF-style membawa field seperti:

{
  "@type": "ProductOffering",
  "@baseType": "CatalogEntity",
  "@schemaLocation": "https://..."
}

Field seperti ini berguna dalam ekosistem tertentu untuk polymorphism dan extension.

Tetapi kalau kita memaksanya ke domain internal, kita akan mendapatkan domain model yang kabur.

Buruk:

public class DomainEntity {
    String atType;
    String atBaseType;
    String atSchemaLocation;
    Map<String, Object> dynamicFields;
}

Ini bukan domain model. Ini generic document container.

Lebih baik:

public final class ProductOffering {
    private final ProductOfferingId id;
    private final ProductSpecificationId productSpecificationId;
    private final OfferingLifecycleStatus status;
    private final List<CharacteristicDefinition> configurableCharacteristics;
    private final List<OfferingPriceRef> prices;
    private final List<EligibilityRuleRef> eligibilityRules;
}

Jika external API perlu @type, mapping-kan di edge.


6. TMF620-Inspired Product Catalog API

TMF620 memberi vocabulary penting untuk Product Catalog Management.

Di platform kita, catalog API dipakai oleh:

  • CPQ UI,
  • configuration engine,
  • pricing engine,
  • quote service,
  • order decomposition,
  • partner API,
  • admin console,
  • cache warmer,
  • operational diagnostics.

6.1 Resource Mapping

API ResourceCanonical EntityOwnership
/product-catalogsCatalogCatalog Service
/product-categoriesCategoryCatalog Service
/product-specificationsProductSpecificationCatalog Service
/product-offeringsProductOfferingCatalog Service
/product-offering-pricesProductOfferingPriceCatalog/Pricing boundary
/product-offering-rulesCompatibility/Eligibility RuleCatalog/Configuration boundary

6.2 Read API Shape

GET /api/v1/product-offerings/{offeringId}

Response konseptual:

{
  "id": "po_broadband_1g",
  "name": "Broadband 1Gbps",
  "lifecycleStatus": "ACTIVE",
  "productSpecification": {
    "id": "ps_broadband_access",
    "name": "Broadband Access"
  },
  "characteristics": [
    {
      "code": "CONTRACT_TERM",
      "valueType": "STRING",
      "allowedValues": ["12M", "24M"],
      "required": true
    }
  ],
  "prices": [
    {
      "id": "pop_broadband_1g_mrc",
      "chargeType": "RECURRING",
      "recurrence": "MONTHLY",
      "amount": {
        "currency": "IDR",
        "value": "499000.00"
      }
    }
  ],
  "validFor": {
    "startDateTime": "2026-01-01T00:00:00Z",
    "endDateTime": null
  }
}

6.3 Important Adaptation

TMF-inspired catalog object biasanya cukup untuk describing offering. Tetapi CPQ kita perlu tambahan:

  • configuration graph,
  • rule explainability,
  • price calculation input,
  • decomposition mapping,
  • quote snapshot policy,
  • cache version.

Jadi kita tambahkan API khusus:

GET /api/v1/product-offerings/{offeringId}/configuration-model
GET /api/v1/product-offerings/{offeringId}/pricing-model
GET /api/v1/product-offerings/{offeringId}/decomposition-template

Jangan paksa semuanya masuk satu resource besar.


7. Catalog Versioning dan Published Immutability

Enterprise catalog bukan data master biasa.

Quote yang dibuat pada 1 Juli harus tetap bisa dijelaskan walaupun catalog berubah pada 15 Juli.

Karena itu product offering perlu version atau published snapshot.

{
  "offeringId": "po_broadband_1g",
  "offeringVersion": 12,
  "catalogVersion": "2026.Q3",
  "lifecycleStatus": "ACTIVE"
}

Quote item menyimpan:

{
  "productOfferingRef": {
    "id": "po_broadband_1g",
    "version": 12,
    "nameSnapshot": "Broadband 1Gbps"
  }
}

Rule:

Quote and order must not depend on mutable live catalog for historical explanation.

8. TMF663 Shopping Cart: Perlu atau Tidak?

Shopping cart berguna jika ada pengalaman belanja sebelum quote.

Contoh:

Customer memilih produk -> cart -> eligibility check -> price estimate -> quote

Tetapi untuk B2B CPQ, quote sering langsung menjadi workspace utama.

Perbedaan:

ConcernCartQuote
Binding commercial promiseLemahKuat
Validity periodPendekFormal
ApprovalJarangSering
Price snapshotEstimasiHarus explainable
Conversion to orderBisaUmum
Sales negotiationRinganInti proses

Keputusan seri:

Kita tidak membuat cart sebagai aggregate utama di awal.
Quote akan berfungsi sebagai configurable commercial workspace.

Nanti jika e-commerce channel butuh cart, cart bisa dibuat sebagai upstream transient aggregate yang mengkonversi ke quote.


9. TMF648-Inspired Quote API

Quote adalah commercial proposal.

Dalam platform kita, quote bukan hanya kumpulan line item. Quote adalah:

  • customer context,
  • offering selections,
  • configuration snapshot,
  • price snapshot,
  • discount/override evidence,
  • approval state,
  • validity period,
  • revision history,
  • conversion candidate.

9.1 Resource Mapping

TMF-like ResourceCanonical Entity
QuoteQuote Aggregate
QuoteItemQuoteItem
ProductRefOrValueConfiguredProductSnapshot
RelatedPartyPartyReference
BillingAccountBillingAccountReference
AgreementAgreementReference
QuotePriceQuotePriceSummary / PriceBreakdown
QuoteStateQuoteStatus

9.2 API Set

POST   /api/v1/quotes
GET    /api/v1/quotes/{quoteId}
PATCH  /api/v1/quotes/{quoteId}
POST   /api/v1/quotes/{quoteId}/items
PATCH  /api/v1/quotes/{quoteId}/items/{itemId}
DELETE /api/v1/quotes/{quoteId}/items/{itemId}
POST   /api/v1/quotes/{quoteId}/price
POST   /api/v1/quotes/{quoteId}/validate
POST   /api/v1/quotes/{quoteId}/submit
POST   /api/v1/quotes/{quoteId}/approve
POST   /api/v1/quotes/{quoteId}/reject
POST   /api/v1/quotes/{quoteId}/accept
POST   /api/v1/quotes/{quoteId}/convert-to-order

TMF-style API cenderung resource-oriented. Kita menambahkan command endpoints untuk lifecycle transition karena state machine harus eksplisit.

9.3 Quote Response Shape

{
  "id": "quo_1001",
  "version": 7,
  "state": "PRICED",
  "stateCategory": "IN_PROGRESS",
  "validFor": {
    "startDateTime": "2026-07-02T00:00:00Z",
    "endDateTime": "2026-07-31T23:59:59Z"
  },
  "relatedParty": [
    {
      "role": "CUSTOMER",
      "id": "party_123",
      "name": "PT Example"
    }
  ],
  "billingAccount": {
    "id": "ba_456"
  },
  "quoteItem": [
    {
      "id": "qi_1",
      "action": "ADD",
      "productOffering": {
        "id": "po_broadband_1g",
        "version": 12,
        "name": "Broadband 1Gbps"
      },
      "configuration": {
        "configurationId": "cfg_1",
        "status": "VALID"
      },
      "price": {
        "oneTimeTotal": { "currency": "IDR", "value": "0.00" },
        "recurringTotal": { "currency": "IDR", "value": "499000.00" }
      }
    }
  ],
  "priceSummary": {
    "currency": "IDR",
    "oneTimeTotal": "0.00",
    "recurringTotal": "499000.00"
  }
}

9.4 Adaptation dari TMF

Kita perlu menambah hal yang biasanya lebih spesifik CPQ:

  • configuration.status,
  • configuration.explanation,
  • price.explanationId,
  • approvalSummary,
  • revisionNumber,
  • sourceQuoteId,
  • convertedOrderId,
  • idempotencyKey handling,
  • command result.

Jangan takut berbeda jika domain membutuhkan.


10. Quote Item: ProductRefOrValue vs Snapshot

TMF-style payload sering memakai konsep product yang bisa berupa reference atau embedded value.

Untuk CPQ kita, quote item harus menyimpan snapshot karena quote adalah promise.

{
  "quoteItemId": "qi_1",
  "productOfferingSnapshot": {
    "id": "po_broadband_1g",
    "version": 12,
    "name": "Broadband 1Gbps"
  },
  "configurationSnapshot": {
    "characteristics": [
      { "code": "CONTRACT_TERM", "value": "24M" }
    ],
    "validationHash": "sha256:..."
  },
  "priceSnapshot": {
    "priceHash": "sha256:...",
    "charges": []
  }
}

Reference ke catalog saja tidak cukup.

Jika catalog berubah, quote tetap harus bisa dijelaskan.


11. TMF622-Inspired Product Ordering API

Product order adalah execution commitment.

TMF622 Product Ordering memberi vocabulary untuk placing product order dengan order parameters berdasarkan product offering dari catalog.

Dalam platform kita, order bisa berasal dari:

  • accepted quote,
  • direct order,
  • amendment,
  • cancellation,
  • migration,
  • repair command.

11.1 Resource Mapping

TMF-like ResourceCanonical Entity
ProductOrderOrder Aggregate
ProductOrderItemOrderItem
ProductOrderItem.actionOrderAction
ProductOrder.stateOrderStatus
ProductOrderedProductSnapshot
RelatedPartyPartyReference
BillingAccountBillingAccountReference
OrderRelationshipRelatedOrderRef

11.2 API Set

POST /api/v1/orders
POST /api/v1/orders/from-quote
GET  /api/v1/orders/{orderId}
GET  /api/v1/orders/{orderId}/timeline
GET  /api/v1/orders/{orderId}/fulfillment-plan
POST /api/v1/orders/{orderId}/submit
POST /api/v1/orders/{orderId}/cancel
POST /api/v1/orders/{orderId}/amend
POST /api/v1/orders/{orderId}/hold
POST /api/v1/orders/{orderId}/resume

11.3 Order from Quote

POST /api/v1/orders/from-quote
Idempotency-Key: idem_123
If-Match: "quote-version-7"

Request:

{
  "quoteId": "quo_1001",
  "requestedCompletionDate": "2026-08-01T00:00:00Z",
  "externalReference": {
    "system": "SALES_PORTAL",
    "id": "sp_9988"
  }
}

Response:

{
  "commandId": "cmd_2001",
  "orderId": "ord_3001",
  "status": "ACCEPTED",
  "statusUrl": "/api/v1/commands/cmd_2001"
}

Order conversion harus idempotent.

Kalau request yang sama dikirim ulang, hasilnya harus order yang sama, bukan order baru.


12. Order Item Action Mapping

Action type penting untuk add/change/disconnect flows.

ActionMeaningInstalled Base Impact
ADDMenambah produk baruCreate new asset/subscription/service
MODIFYMengubah produk terpasangCreate new asset version or update subscription terms
DISCONNECTMengakhiri produkTerminate asset/subscription/service
MOVEMemindahkan lokasi/ownershipUpdate relationship/place; may require technical re-provisioning
SUSPENDSuspend sementaraChange service/subscription operational status
RESUMEAktifkan kembaliReverse suspend if allowed

Jangan model semua sebagai UPDATE.

OMS butuh action karena decomposition dan compensation berbeda.


13. TMF637-Inspired Product Inventory API

Product inventory merepresentasikan produk yang sudah dimiliki/aktif untuk customer.

Dalam platform kita:

Product Inventory ~= Installed Base / Asset View

Resource:

GET /api/v1/customer-assets?customerId=party_123
GET /api/v1/customer-assets/{assetId}
GET /api/v1/customer-assets/{assetId}/timeline
GET /api/v1/customer-assets/{assetId}/eligible-actions

Mapping:

TMF-like ConceptModel Kita
ProductCustomerAsset / InstalledProduct
Product.statusAssetStatus
Product.productOfferingOffering Snapshot
Product.productCharacteristicInstalled Characteristic
ProductRelationshipAssetRelationship
PlaceServiceLocationRef
RelatedPartyCustomer/User/Owner

Installed base penting untuk CPQ karena modify/disconnect quote membutuhkan current asset snapshot.

Contoh:

{
  "assetId": "ast_1001",
  "customerId": "party_123",
  "status": "ACTIVE",
  "productOffering": {
    "id": "po_broadband_1g",
    "name": "Broadband 1Gbps"
  },
  "characteristics": [
    { "code": "CONTRACT_TERM", "value": "24M" },
    { "code": "ACCESS_SPEED", "value": "1G" }
  ],
  "subscription": {
    "id": "sub_1001",
    "status": "ACTIVE",
    "startDate": "2026-01-01T00:00:00Z"
  }
}

14. TMF633/TMF641-Inspired Service Layer

Commercial order tidak selalu langsung dipenuhi oleh satu technical action.

Contoh commercial product:

Broadband 1Gbps + Static IP + Router Rental

Bisa decomposed menjadi:

- reserve access resource
- ship router
- configure CPE
- provision broadband service
- assign static IP
- activate billing trigger
- send notification

TMF633 Service Catalog dan TMF641 Service Ordering memberi inspirasi untuk technical fulfillment boundary.

Platform kita akan memakai internal model:

Commercial LayerTechnical Layer
ProductOfferingTechnicalServiceSpecification mapping
ProductOrderItemFulfillmentPlanItem
Order DecompositionTechnicalFulfillmentOrder generation
Order StateAggregated from fulfillment task states
Product InventoryService Inventory + Asset View

API internal:

POST /internal/v1/technical-fulfillment-orders
GET  /internal/v1/technical-fulfillment-orders/{id}
POST /internal/v1/technical-fulfillment-orders/{id}/tasks/{taskId}/complete
POST /internal/v1/technical-fulfillment-orders/{id}/tasks/{taskId}/fail

Jangan expose technical service order langsung ke sales channel kecuali memang consumer-nya technical.


15. End-to-End Mapping Flow

TMF vocabulary membantu naming resource. Internal orchestration tetap milik desain kita.


16. API Mapping per Capability

16.1 Catalog Browse

CapabilityAPI
Browse offeringsGET /api/v1/product-offerings
Get offering detailGET /api/v1/product-offerings/{id}
Get configuration modelGET /api/v1/product-offerings/{id}/configuration-model
Get price modelGET /api/v1/product-offerings/{id}/pricing-model
Check eligibilityPOST /api/v1/product-offerings/{id}/check-eligibility

16.2 CPQ

CapabilityAPI
Create quotePOST /api/v1/quotes
Add quote itemPOST /api/v1/quotes/{id}/items
Configure itemPATCH /api/v1/quotes/{id}/items/{itemId}/configuration
Price quotePOST /api/v1/quotes/{id}/price
Validate quotePOST /api/v1/quotes/{id}/validate
Submit quotePOST /api/v1/quotes/{id}/submit
Accept quotePOST /api/v1/quotes/{id}/accept
Convert quotePOST /api/v1/quotes/{id}/convert-to-order

16.3 OMS

CapabilityAPI
Create direct orderPOST /api/v1/orders
Create order from quotePOST /api/v1/orders/from-quote
Get orderGET /api/v1/orders/{id}
Get timelineGET /api/v1/orders/{id}/timeline
Get fulfillment planGET /api/v1/orders/{id}/fulfillment-plan
Cancel orderPOST /api/v1/orders/{id}/cancel
Hold/resume orderPOST /api/v1/orders/{id}/hold, POST /api/v1/orders/{id}/resume

16.4 Installed Base

CapabilityAPI
Search customer assetsGET /api/v1/customer-assets
Get asset detailGET /api/v1/customer-assets/{id}
Get eligible actionsGET /api/v1/customer-assets/{id}/eligible-actions
Quote modify existing assetPOST /api/v1/quotes/from-asset-action

17. Relationship Modelling

TMF-style APIs often use relationships heavily.

Example:

{
  "relatedParty": [
    { "role": "CUSTOMER", "id": "party_123" },
    { "role": "SALES_AGENT", "id": "user_99" }
  ],
  "relatedOrder": [
    { "relationshipType": "AMENDS", "id": "ord_1000" }
  ]
}

Relationship is useful, but dangerous if unconstrained.

Policy:

Every relationship type must be enumerated and have domain meaning.

Do not allow arbitrary strings like:

{ "relationshipType": "somehow_related" }

For our model:

RelationshipMeaning
QUOTE_DERIVED_FROMQuote revision from previous quote
ORDER_DERIVED_FROM_QUOTEOrder created from accepted quote
ORDER_AMENDS_ORDERAmendment order modifies previous order
ORDER_CANCELS_ORDERCancellation order targets previous order
ITEM_MODIFIES_ASSETOrder item modifies installed asset
ITEM_DISCONNECTS_ASSETOrder item terminates installed asset
ITEM_DEPENDS_ON_ITEMFulfillment dependency

Relationship must support traceability, not become dumping ground.


18. External Identifier Strategy

Enterprise integrations need external references.

Examples:

  • CRM opportunity ID,
  • partner order ID,
  • billing account ID,
  • provisioning service ID,
  • legacy asset ID.

Model:

{
  "externalIdentifier": [
    {
      "system": "CRM",
      "type": "OPPORTUNITY_ID",
      "id": "opp_789"
    },
    {
      "system": "PARTNER_X",
      "type": "ORDER_ID",
      "id": "PX-2026-0001"
    }
  ]
}

Rules:

Internal ID remains primary.
External ID is lookup/reference, not aggregate identity.
External ID uniqueness must be scoped by system + type.
External ID changes must be audited.

Do not use partner ID as primary key.


19. State Mapping

TMF-style state values may not match internal state machine exactly.

Example internal order states:

DRAFT
ACKNOWLEDGED
VALIDATED
DECOMPOSED
FULFILLING
PARTIALLY_COMPLETED
COMPLETED
FAILED
CANCELLING
CANCELLED
FALLOUT
CLOSED

External simplified states:

ACKNOWLEDGED
IN_PROGRESS
COMPLETED
FAILED
CANCELLED

Mapping:

Internal StateExternal StateCategory
DRAFTACKNOWLEDGED or hiddenDRAFT
ACKNOWLEDGEDACKNOWLEDGEDIN_PROGRESS
VALIDATEDIN_PROGRESSIN_PROGRESS
DECOMPOSEDIN_PROGRESSIN_PROGRESS
FULFILLINGIN_PROGRESSIN_PROGRESS
PARTIALLY_COMPLETEDIN_PROGRESSIN_PROGRESS
COMPLETEDCOMPLETEDSUCCESS
FAILEDFAILEDFAILURE
FALLOUTIN_PROGRESS or FAILEDdepends on policy
CANCELLEDCANCELLEDTERMINAL

Never let external simplification destroy internal state detail.


20. Price Mapping

TMF-style ProductOfferingPrice is catalog price.

CPQ price result is not just ProductOfferingPrice.

We need distinguish:

ConceptMeaning
ProductOfferingPricePrice defined in catalog
QuoteItemPriceCalculated price for selected configuration/customer/context
PriceAlterationDiscount/promotion/override/adjustment
PriceSummaryAggregated totals
PriceExplanationWhy price became that number
Billing ChargeDownstream billing instruction or charge candidate

Bad design:

Store only final price total.

Good design:

{
  "price": {
    "charges": [
      {
        "chargeType": "RECURRING",
        "recurrence": "MONTHLY",
        "baseAmount": { "currency": "IDR", "value": "599000.00" },
        "adjustments": [
          {
            "type": "PROMOTION",
            "code": "PROMO_100K_OFF",
            "amount": { "currency": "IDR", "value": "-100000.00" }
          }
        ],
        "finalAmount": { "currency": "IDR", "value": "499000.00" }
      }
    ],
    "explanationId": "pex_123",
    "priceHash": "sha256:..."
  }
}

TMF vocabulary helps. CPQ explainability completes it.


21. Configuration Mapping

TMF ProductCharacteristic can represent selected characteristics.

But CPQ configuration needs more:

  • allowed values,
  • dependency,
  • exclusion,
  • cardinality,
  • compatibility,
  • source of default,
  • validation result,
  • conflict explanation.

So we separate:

ConcernAPI
What can be configuredCatalog/configuration model API
What customer selectedQuote item configuration
Whether validConfiguration validation result
Why invalidExplanation/conflict result

Example:

{
  "configuration": {
    "status": "INVALID",
    "selectedCharacteristics": [
      { "code": "CONTRACT_TERM", "value": "12M" },
      { "code": "ROUTER_OPTION", "value": "PREMIUM_ROUTER" }
    ],
    "violations": [
      {
        "code": "INCOMPATIBLE_OPTION",
        "message": "Premium router requires contract term 24M.",
        "path": "/selectedCharacteristics/1"
      }
    ]
  }
}

Do not hide configuration validation behind generic 400 error only.


22. Approval Mapping

TMF Quote API can represent quote state. But approval process often needs richer internal model.

Our approval API:

GET  /api/v1/quotes/{quoteId}/approval-summary
GET  /api/v1/quotes/{quoteId}/approval-tasks
POST /api/v1/quotes/{quoteId}/approval-tasks/{taskId}/approve
POST /api/v1/quotes/{quoteId}/approval-tasks/{taskId}/reject
POST /api/v1/quotes/{quoteId}/approval-tasks/{taskId}/delegate

Why not only PATCH /quotes/{id}?

Because approval is not just updating state. It has:

  • actor,
  • authority,
  • decision,
  • reason,
  • timestamp,
  • policy version,
  • escalation,
  • delegation,
  • evidence.

Approval commands must be auditable.


23. Anti-Corruption Layer for TMF-Compatible Integration

If an external partner wants TMF-like payload, do not force internal service to speak that shape everywhere.

Use adapter:

Adapter responsibilities:

  • validate external schema,
  • map external ID to internal ID,
  • normalize state/value naming,
  • reject unsupported relationship types,
  • enrich missing mandatory internal context,
  • map errors to partner contract,
  • record audit mapping,
  • preserve external identifiers.

Application service should not know partner-specific quirks.


24. Canonical Command Examples

External quote create request may be TMF-like. Internally convert to command:

public record CreateQuoteCommand(
    CustomerRef customer,
    BillingAccountRef billingAccount,
    SalesChannelRef salesChannel,
    AgreementRef agreement,
    List<ExternalIdentifier> externalIdentifiers,
    Instant requestedValidUntil,
    ActorRef actor,
    RequestContext requestContext
) {}

Add item command:

public record AddQuoteItemCommand(
    QuoteId quoteId,
    ProductOfferingId offeringId,
    CatalogVersionRef catalogVersion,
    OrderAction action,
    Optional<AssetId> targetAssetId,
    List<SelectedCharacteristic> selectedCharacteristics,
    ActorRef actor,
    IdempotencyKey idempotencyKey
) {}

Order from quote:

public record CreateOrderFromQuoteCommand(
    QuoteId quoteId,
    QuoteVersion expectedQuoteVersion,
    RequestedCompletionDate requestedCompletionDate,
    List<ExternalIdentifier> externalIdentifiers,
    ActorRef actor,
    IdempotencyKey idempotencyKey
) {}

Notice: command internal lebih eksplisit daripada external JSON.


25. Event Mapping

Event internal kita harus canonical, bukan copy response API.

EventTriggerPayload Principle
ProductOfferingPublishedCatalog publishIDs, version, lifecycle, cache invalidation hints
QuoteCreatedQuote createdQuote ID, customer, channel, actor
QuoteItemConfiguredConfiguration changedItem ID, config status, config hash
QuotePricedPricing completedPrice hash, total summary, explanation ID
QuoteSubmittedSubmitted for approval/orderQuote version, approval required flag
QuoteAcceptedCustomer accepts quoteQuote version, acceptedAt
OrderCreatedFromQuoteConversion successQuote ID/version, order ID
OrderSubmittedOrder submittedOrder ID/version
OrderDecomposedFulfillment plan createdPlan ID/version
FulfillmentTaskCompletedTask completeTask ID, order item ID, result
CustomerAssetActivatedInstalled base updatedAsset ID, order item ID

Do not publish entire aggregate if consumers only need signal + reference.

Event should support replay and not expose private internal object graph.


26. API Facade vs Domain Service API

For external TMF-like integration, it may be useful to expose facade APIs:

/tmf/v5/productCatalogManagement/...
/tmf/v5/quoteManagement/...
/tmf/v5/productOrdering/...

But internal canonical APIs remain:

/api/v1/product-offerings
/api/v1/quotes
/api/v1/orders

Architecture:

This prevents a standard facade from dictating the whole internal design.


27. How to Decide Whether to Be TMF-Compatible

Ask:

1. Do external partners require TMF conformance?
2. Do we need certification/interoperability?
3. Are downstream systems already TMF-based?
4. Is the domain telco-like enough?
5. Can our internal requirements fit without distortion?
6. Will conformance improve integration speed or create ceremony?

If yes, expose TMF-compatible facade.

If no, still use vocabulary and mapping discipline.

For this learning series, we choose:

TMF-inspired internal canonical API.
Optional TMF-compatible adapter/facade later.

28. Data Ownership Mapping

DataOwnerExposed Via
Product offeringCatalog ServiceCatalog API
Configuration modelCatalog/Configuration ServiceConfiguration API
Calculated pricePricing/Quote ServiceQuote/Pricing API
QuoteQuote ServiceQuote API
Approval taskApproval/Workflow boundaryApproval API
Product orderOrder ServiceOrder API
Fulfillment planOMS/Fulfillment boundaryFulfillment API
Technical taskFulfillment ServiceInternal Fulfillment API
Installed assetInventory/Asset ServiceCustomer Asset API
PartyCRM/Party systemParty reference, not copied blindly
Billing accountBilling/Account systemBillingAccountRef
AgreementCLM/Agreement systemAgreementRef

Do not let API mapping blur ownership.


29. OpenAPI Tag Strategy

OpenAPI document should be organized by capability, not by database table.

Example tags:

tags:
  - name: Product Offerings
    description: Published commercial offerings available for selection.
  - name: Configuration
    description: Product configuration models and validation operations.
  - name: Pricing
    description: Price simulation and quote pricing operations.
  - name: Quotes
    description: Commercial proposal lifecycle.
  - name: Quote Approvals
    description: Approval tasks and decisions related to quotes.
  - name: Orders
    description: Product order capture and lifecycle.
  - name: Fulfillment
    description: Fulfillment plan and operational task visibility.
  - name: Customer Assets
    description: Installed base and subscription-oriented views.

Bad tags:

quote_table
order_item_table
product_json
misc

Tagging is documentation architecture.


30. Example OpenAPI Fragment: Quote Command

paths:
  /quotes/{quoteId}/submit:
    post:
      tags:
        - Quotes
      operationId: submitQuote
      summary: Submit a quote for approval or acceptance flow
      parameters:
        - name: quoteId
          in: path
          required: true
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
        - name: If-Match
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SubmitQuoteRequest'
      responses:
        '202':
          description: Quote submit command accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CommandAcceptedResponse'
        '409':
          description: Invalid quote state or version conflict
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'

This is not pure CRUD. It is lifecycle command.


31. Example Mapping: External TMF-like Quote to Internal Command

External input:

{
  "relatedParty": [
    { "role": "customer", "id": "party_123" },
    { "role": "salesAgent", "id": "usr_99" }
  ],
  "billingAccount": { "id": "ba_456" },
  "externalIdentifier": [
    { "owner": "CRM", "id": "opp_123" }
  ],
  "validFor": {
    "endDateTime": "2026-08-01T00:00:00Z"
  }
}

Internal command:

{
  "customer": { "id": "party_123" },
  "actor": { "id": "usr_99", "type": "SALES_AGENT" },
  "billingAccount": { "id": "ba_456" },
  "externalIdentifiers": [
    { "system": "CRM", "type": "OPPORTUNITY_ID", "id": "opp_123" }
  ],
  "requestedValidUntil": "2026-08-01T00:00:00Z"
}

Mapping rules catch problems:

  • missing customer,
  • multiple customer parties,
  • unsupported party role,
  • invalid billing account reference,
  • invalid external identifier owner.

32. Error Mapping

Internal error:

{
  "code": "QUOTE_INVALID_STATE_TRANSITION",
  "status": 409,
  "detail": "Quote cannot move from ACCEPTED to SUBMITTED."
}

External partner contract may need:

{
  "code": "INVALID_TRANSITION",
  "reason": "The requested operation is not allowed for the current quote state.",
  "message": "Quote cannot be submitted because it has already been accepted."
}

Do not leak internal error taxonomy if partner contract differs.

But keep correlation:

internalErrorId -> externalResponseId -> audit record

33. Security and Authorization Mapping

TMF-like API says what resource exists. It does not decide your authorization model.

For CPQ/OMS:

OperationAuthorization Concern
Browse offeringMarket/segment/channel eligibility
Create quoteSales channel, customer access
Apply discountDiscount authority
Override priceApproval or privileged role
Submit quoteQuote ownership, state
Approve quoteApproval assignment, delegation
Convert to orderAccepted quote, customer authority
Cancel orderOrder state, role, regulatory/commercial policy
Modify assetAsset ownership, eligibility, contract terms

Authorization must sit in application layer, not merely API gateway.


34. Testing Mapping Correctness

Test types:

TestPurpose
Contract validationExternal payload matches API schema
Mapping testDTO maps to canonical command correctly
Rejection testUnsupported relationship/role rejected
Round-trip projection testCanonical projection maps to external response
Golden partner payload testExisting partner examples stay supported
Error mapping testInternal error maps to expected external error
State mapping testInternal state maps to external category correctly
Audit mapping testExternal IDs and transformed fields traceable

Example test fixture structure:

test-fixtures/
  tmf-like/
    quote-create/
      valid-minimal.json
      valid-with-external-id.json
      invalid-multiple-customer.json
      invalid-unsupported-party-role.json
    product-order/
      order-from-quote.json
      amend-existing-asset.json

Mapping code is production code. Test it like business logic.


35. Failure Modes

35.1 Standard Shape, Wrong Semantics

Payload follows TMF-like structure but means the wrong thing.

Example:

Partner sends ProductOrderItem.action = modify, but no target asset reference.

Schema may pass. Domain validation must reject.

35.2 Relationship Explosion

Everything is represented as relationship.

Result:

No one knows which relationship drives behavior.

Fix:

Enumerate relationship types and map each to explicit domain meaning.

35.3 Generic Extension Abuse

Partner puts business-critical fields in extension bag.

Fix:

Promote stable business-critical field into explicit contract.
Use extension only for non-core partner metadata.

35.4 External State Drives Internal State Machine

External consumer sends state directly:

{ "state": "COMPLETED" }

This bypasses lifecycle control.

Fix:

Expose command endpoints, not arbitrary state patching.

35.5 Product Offering Used as Installed Product

A product offering is what can be sold. Installed product/asset is what customer has.

Mixing them breaks modify/disconnect.

Fix:

Use ProductOfferingRef for catalog.
Use Asset/ProductInventoryRef for installed base.

36. Design Rules for Our Platform

Use these rules going forward:

1. TMF vocabulary is allowed; TMF cargo-cult is not.
2. External API DTO is not domain model.
3. All external payloads map to canonical commands.
4. Quote item stores offering/configuration/price snapshots.
5. Order item action is explicit.
6. Installed asset is separate from product offering.
7. Relationship types are enumerated and behavior-relevant.
8. External identifiers are scoped references, not primary keys.
9. State mapping must preserve internal detail.
10. Approval, pricing, configuration, and fulfillment need first-class APIs, not generic patching.
11. TMF-compatible facade may exist, but canonical service remains internally consistent.
12. Mapping code must be tested and audited.

37. Architecture Decision Record

# ADR-018: TM Forum Inspired API Mapping

## Status
Accepted

## Context
The CPQ/OMS platform must support enterprise commerce concepts such as product catalog, quote, product order, product inventory, service fulfillment, related party, billing account, and agreement references. TM Forum Open APIs provide useful industry vocabulary, but the platform also requires CPQ-specific configuration, pricing explainability, approval policy, orchestration, and operational repair models.

## Decision
Use TM Forum Open APIs as vocabulary and integration inspiration, not as internal domain model. Build canonical internal APIs and domain commands. Add TMF-compatible facade or partner adapters only at the edge where required.

## Consequences
Internal models remain precise and invariant-driven. External integrations can still align with industry vocabulary. Mapping layers become critical production components and require contract tests, golden payloads, error mapping, and audit traceability.

38. Implementation Milestone for Our Platform

Setelah part ini, kita punya API mapping direction:

1. Catalog API inspired by TMF620 vocabulary.
2. Quote API inspired by TMF648 but extended for CPQ configuration/pricing/approval.
3. Order API inspired by TMF622 but extended for decomposition and fulfillment visibility.
4. Customer Asset API inspired by product inventory concepts.
5. Internal fulfillment API inspired by service ordering concepts.
6. Anti-corruption layer for external TMF-like payloads.
7. Canonical command model for application services.
8. Explicit mapping tests and golden payloads.

Part berikutnya kita akan mulai masuk ke implementasi Java runtime foundation: Jakarta REST/JAX-RS, Jersey, GlassFish, request lifecycle, provider, filter, exception mapper, dan dependency boundaries.


39. Ringkasan

TM Forum-inspired mapping membantu kita berbicara dalam vocabulary enterprise yang matang.

Tetapi platform CPQ/OMS production tidak boleh menjadi copy generik dari standard payload.

Desain yang benar:

External vocabulary for interoperability.
Internal canonical model for correctness.
Explicit mapping for safety.
State machines for lifecycle control.
Snapshots for commercial defensibility.
Events for integration.
Audit for accountability.

Dengan ini, kita siap membangun runtime Java-nya.


40. Referensi Resmi yang Relevan

Lesson Recap

You just completed lesson 18 in build core. 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.