Writing

URN and the Adapter Pattern in Microservice Architectures

How Uniform Resource Names plus the Adapter Pattern create a stable canonical model that survives vendor churn, supports multi-tenancy, and turns infrastructure changes into local edits.

February 25, 2025·9–11 min read·English
MicroservicesURNAdapter PatternArchitectureMulti-TenantDDD

Modern microservice architectures often need to integrate with multiple external systems and services. Each external vendor API may use different identifiers and data formats, posing a challenge for seamless system interconnection. A key strategy to address this is the use of Uniform Resource Names (URNs) as standardized identifiers across the microservice ecosystem.

A URN is a particular type of URI that uniquely names a resource without implying its location or how to retrieve it. Unlike a URL, a URN is solely concerned with what the resource is, not where it is — offering a stable, location-independent handle.

This essay explores the role of URNs in achieving seamless system interconnection within microservice architectures, drawing on the Adapter Pattern for integration. It discusses how URNs facilitate a structured canonical model (not just an arbitrary "bag of cats"), enforce clear business definitions decoupled from vendor specifics, enable easy integration of new services while maintaining strong control, and support multi-tenant environments with accurate billing.

01Foundations

URNs in microservice integration

Using URNs as a uniform identification scheme can significantly simplify integration between services. URNs provide globally unique, persistent identifiers that are independent of any single service's database or location. Instead of services exchanging raw IDs or URLs tied to specific databases, they share URNs like urn:order:12345. Because the URN does not encode location, any service can interpret and route it to the appropriate handler without tight coupling.

Seamless interconnection

Once a resource has a uniform name, services can attach metadata (region, owner, tenant) and automate routing, monitoring, or billing. One service can emit an event including a URN (e.g. urn:customer:alice) and any other service — regardless of language or location — can interpret what Alice refers to and how to process it.

The Adapter Pattern simplifies how URNs are leveraged: microservices rely on a well-defined interface to reach vendor-specific functionality. URNs are the common thread that weaves diverse components together without entangling their internals.

02The model

Canonical model and vendor agnosticism

A canonical data model is a structured representation of business entities that all services agree on. It is not an amorphous collection of every possible field from every vendor — not a "bag of cats" — but an organized schema representing core business concepts.

URNs play a key role in enforcing this structure. Each URN encodes a resource according to canonical definitions — namespace or entity type, unique identifier, and optionally structured attributes that provide context. A common pattern: urn:<entity>:<id>[:<key>:<value>]*.

const urn = Urn.compose({
  entity: "order",
  id: "12345",
  attributes: { vendor: "amazon", status: "shipped" }
});
console.log(urn);
// => urn:order:12345:vendor:amazon:status:shipped

order is the entity from the canonical model, 12345 the internal ID, and vendor:amazon / status:shipped are structured attributes adding context. The canonical model stays clean and consistent; vendor-specific facts are captured as add-on attributes rather than polluting core domain types.

If two vendors supply similar data in different shapes, internally they are represented uniformly. Fields that don't fit the model are handled by adapter translation code — the anti-corruption layer — instead of leaking into core services. URNs ensure that the identity of resources in the internal domain remains consistent and meaningful, insulating the core from external noise.

03The pattern

Adapter Pattern and URN integration

Adapter services act as intermediaries between internal microservices and external vendor systems. Each adapter translates internal requests (identified by URNs and using canonical structures) into vendor-specific API calls — and the other way around.

How URNs are used with adapters

Suppose our system supports multiple shipping providers, each with its own API. We define a canonical shipment entity and identify shipments with URNs such as urn:shipment:55555:vendor:fedex or urn:shipment:44444:vendor:ups. The internal Shipping Service requests tracking info via a URN, never calling external APIs directly:

function getShipmentStatus(shipmentUrn):
    parts  = Urn.parse(shipmentUrn)
    entity = parts.entity              # "shipment"
    id     = parts.id                  # "55555"
    vendor = parts.attributes.vendor   # "fedex"

    adapter = AdapterRegistry.getAdapter(entity, vendor)
    return adapter.fetchStatus(id)

The Shipping Service does not need to know anything about FedEx vs UPS. The adapter (e.g. FedExAdapter) makes the external call and maps the result back into canonical form. Adding a new shipping provider becomes plug-and-play: implement a new adapter, register it with the registry. The integration is uniform thanks to the URN contract.

Maintaining strong control

The organization keeps full control over internal data definitions. The URN scheme and canonical model are dictated by internal business logic — what a valid shipment URN is and which attributes it may carry. External systems do not dictate this schema; they only interface through the adapter. If one vendor returns status="shp" and another status="in_transit", the adapter maps them to a canonical status:shipped.

Developers benefit from URN utility libraries (such as @jescrich/urn) that compose, parse, and validate URNs — ensuring every URN in the system conforms to the expected pattern. A developer reading an event orderUpdated: urn:order:12345:status:shipped can immediately understand its meaning.

04Decoupling

Infrastructure freedom through URNs and adapters

Microservices treat resources abstractly via URNs while the adapter layer handles concrete implementation. If a vendor changes protocols, APIs, or schemas, only the relevant adapter needs updating. The rest of the system remains untouched. This is dependency inversion at the system-integration level: URNs specify what, adapters handle how.

URN namespaces also enable dynamic routing: configure rules so calls containing urn:customer: route to the Customer Service, urn:order: to the Order Service, and so on. Infrastructure changes stay local; the URN-based interface remains stable.

05Multi-tenancy

Tenant context and billing support

Extending URNs to encode tenant context yields straightforward partitioning and accounting per tenant. Tenant ACME's customer 100 becomes urn:customer:100:tenant:acme; tenant Globex's customer 100 becomes urn:customer:100:tenant:globex. Both represent distinct resources.

Every time a service processes a resource or logs an event, it includes the URN. A downstream analytics or billing service filters or groups URNs by tenant attribute. Seeing 100 occurrences of urn:customer:*:tenant:acme attributes them to ACME's bill — mirroring how cloud providers track resource usage via ARNs.

URN-based access control becomes simple too: ensure tenant X can only access URNs that contain tenant:x. Both isolation and precise billing become manageable.

06Closing

A foundational contract

URNs serve as a foundational tool for achieving seamless yet controlled interconnection in microservice architectures. By abstracting resource identity away from vendor-specific formats and physical locations, they let a system center around a stable, well-structured canonical model.

URNs empower organizations to define their internal data structures independent of external constraints — facilitating a microservice ecosystem that is at once highly integrable and strongly governed.
José Escrich

Fractional CTO and software architect. Built in Bariloche, Patagonia — working with teams worldwide.

© 2020–2026 José Escrich. All rights reserved.
Designed & built by @jes