Chapter 19 · Composite Databases, Multiple Databases, Federation, and Data-Domain Boundaries
Design a Multi-Domain Graph Architecture and Decide Which Relationships Belong Local vs Federated
Produce an evidence-based AtlasMart multi-domain architecture, place each relationship where its invariants are owned, choose local traversal versus federation deliberately, and define failure, recovery, migration, and rollback consequences.
The AtlasMart architecture board must choose between one connected graph, multiple domain databases, separate instances, and an optional Enterprise composite. The decision cannot be “microservices are modern” or “graphs should connect everything.” It must preserve the invariants and traversals that deliver business value while making ownership, security, capacity, failure and recovery tractable. This final lab produces that decision record.
A relationship belongs local when its traversal and invariant are owned together. A relationship becomes a federated reference only when the domain boundary is more valuable than the lost graph-local atomicity/traversal.
Learning outcomes
Classify AtlasMart entities/relationships by owner, atomicity, traversal frequency and security/recovery boundary.
Choose local relationships versus proxy-key federation with explicit reasoning and measurable costs.
Design the free Community multi-instance architecture and optional Enterprise composite without conflating their semantics.
Specify SLO, failure, backup, security, version, migration and rollback evidence for every constituent dependency.
Produce a final go/no-go checklist that can reject federation when it harms correctness or dominant workload performance.
Reproducible Chapter 19 lab baseline
Current self-managed Neo4j is 2026.07.1; current
5.26 LTS is 5.26.30. The course remains on Java
21 or 25, explicit CYPHER 25 for
version-sensitive examples, and Python driver 6.3. The
mandatory chapter lab uses three disposable Neo4j Community
2026.07.1 instances because Community can host exactly one
standard database per DBMS. No runtime output or latency value
in these lessons is claimed to have been executed during
generation; deterministic expected rows are fixture invariants
and timings must be measured by the learner.
Self-managed
Community Edition can have exactly one standard
database. Self-managed
Enterprise Edition can have multiple standard
databases; CREATE DATABASE and composite-database
administration are Enterprise features and are not available
on Aura. Composite databases are Enterprise-only and
explicitly unavailable on Aura. Therefore the free learning
path uses separate Community DBMS instances and
application-side federation; optional Enterprise commands are
labeled and must not be mistaken for Community or Aura
behavior.
| Term | Mechanism-first meaning |
|---|---|
| standard database | A physical Neo4j database that contains one graph in Neo4j 2026.07; it is an execution context and transaction domain. |
| DBMS | A Neo4j database-management process/deployment that hosts the system database plus the standard databases allowed by its edition. |
| system database | Built-in metadata/security database used for database, alias, server and access administration; it does not contain AtlasMart domain graph data. |
| multiple databases | Several standard databases managed by one Enterprise DBMS; separation is stronger than labels but still shares DBMS/server resources and operations. |
| composite database | Enterprise logical execution/federation context containing aliases to constituent graphs; it stores no graph data independently. |
| constituent | A local or remote standard database exposed inside a composite through a namespaced alias. |
| local alias | Alias whose target standard database is in the same DBMS. |
| remote alias | Alias whose target is another Neo4j DBMS over a driver connection and whose authentication/security is governed at that remote boundary. |
| federated query | One Cypher query whose graph-specific subqueries read from more than one constituent. |
| location transparency | The caller uses a logical constituent name while the alias determines whether its target is local or remote; latency/failure locality is not magically erased. |
| proxy node | A deliberately duplicated identity-only node used to join facts across disjoint graphs because Neo4j relationships cannot span graphs. |
| transaction domain | The set of graph updates that can commit atomically together. A standard database is one transaction domain; a composite permits multi-graph reads but updates only one constituent per transaction. |
| tenant boundary | A technical/operational separation choice for tenant data. Database separation does not automatically provide CPU/memory/noisy-neighbor isolation, billing isolation, or legal compliance. |
| domain ownership | The team/system accountable for a fact’s schema, invariants, writes, recovery and lifecycle—not merely the graph where a convenient copy exists. |
| Community instance | Purpose | HTTP | Bolt | Container | Volume |
|---|---|---|---|---|---|
| catalog | Product/catalog source of truth | 7574 | 7767 | atlasmart-ch19-catalog | atlasmart-ch19-catalog-data |
| orders | Order facts plus CustomerRef/ProductRef proxies | 7575 | 7768 | atlasmart-ch19-orders | atlasmart-ch19-orders-data |
| customers | Customer source of truth | 7576 | 7769 | atlasmart-ch19-customers | atlasmart-ch19-customers-data |
| Assumption | Pinned value / rule |
|---|---|
| deployment | Three isolated local Community containers on one workstation; this simulates domain separation, not a composite database |
| database | Each Community DBMS uses its single standard database named neo4j |
| auth | Synthetic lab-only neo4j / atlasmart-course-2026 credential; never use it outside the disposable lab |
| TLS | Loopback lab uses bolt:// for simplicity; remote production aliases/drivers require verified TLS and credential governance |
| plugins | No APOC or GDS required |
| indexes | Uniqueness constraints on domain IDs only; no cross-database constraint exists |
| graph size | Tiny deterministic fixture: 3 products, 3 customers, 3 orders, 4 line items/proxy references |
| failure injection | Stop one disposable instance or add an application-side artificial delay; no destructive network or disk fault is required |
| Enterprise option | Commands are examples for a licensed self-managed Enterprise environment and are not executed by the free path |
| Aura | Composite databases and self-managed CREATE DATABASE are not taught as Aura capabilities |
1. Relationship placement matrix
| Relationship/fact | Recommended owner/location | Why |
|---|---|---|
| Order-PLACED_BY-CustomerRef | Orders local | Order aggregate must reference customer identity without remote edge |
| Order-CONTAINS-ProductRef + quantity/unitPrice | Orders local | line quantity and charged price are order invariants/history |
| Customer profile/tier | Customers local | PII/profile lifecycle and security owner |
| Product current price/category | Catalog local | current merchandising truth and catalog lifecycle |
| CustomerRef ↔ Customer | federated equality on customerId, not relationship | different domains; relationship cannot span graphs |
| ProductRef ↔ Product | federated equality on productId, not relationship | different domains; join only when current catalog data is required |
2. Reject a split when it breaks the dominant graph workload
| Observed workload | Decision pressure |
|---|---|
| 80% of requests traverse Order→Product→Supplier→Inventory and require strict same-transaction changes | keep/recompose those invariants locally; federation is likely wrong boundary |
| Catalog writes independently; Orders mostly needs immutable line snapshot and occasional current product display | domain split with ProductRef is plausible |
| Customer PII requires independent access/recovery lifecycle | strong reason for Customer boundary; expose only minimal identity/reference data |
| cross-domain analytics runs hourly and tolerates seconds | federation/read model can be appropriate; optimize batch/selectivity rather than request-time N+1 |
3. Final free architecture
| Component | Free Chapter 19 implementation | Production analogue |
|---|---|---|
| Catalog | Community DBMS on 7767 | own DBMS/managed instance or Enterprise standard database |
| Orders | Community DBMS on 7768 | own DBMS/managed instance or Enterprise standard database |
| Customers | Community DBMS on 7769 | own DBMS/managed instance or Enterprise standard database |
| Federation | Python service batches stable IDs and joins values | service/read-model federation or optional Enterprise composite |
| Identity | ProductRef/CustomerRef constraints | versioned business identity contract + reconciliation |
| Observability | per-domain timings/errors + total latency | distributed tracing/metrics/logs with constituent attribution |
from neo4j import GraphDatabase
from time import perf_counter
AUTH = ("neo4j", "atlasmart-course-2026")
drivers = {
"orders": GraphDatabase.driver("bolt://127.0.0.1:7768", auth=AUTH),
"customers": GraphDatabase.driver("bolt://127.0.0.1:7769", auth=AUTH),
"catalog": GraphDatabase.driver("bolt://127.0.0.1:7767", auth=AUTH),
}
def run(domain, query, **params):
t0 = perf_counter()
with drivers[domain].session(database="neo4j") as s:
rows = [r.data() for r in s.run(query, **params)]
return rows, perf_counter() - t0
order_rows, t_orders = run("orders", """
MATCH (o:Order {orderId:$orderId})-[:PLACED_BY]->(cr:CustomerRef)
MATCH (o)-[li:CONTAINS]->(pr:ProductRef)
RETURN o.orderId AS orderId, o.status AS status,
cr.customerId AS customerId, pr.productId AS productId,
li.quantity AS quantity, li.unitPrice AS unitPrice
ORDER BY productId
""", orderId="O-1901")
customer_id = order_rows[0]["customerId"]
product_ids = [r["productId"] for r in order_rows]
customer_rows, t_customers = run("customers", """
MATCH (c:Customer {customerId:$id})
RETURN c.customerId AS customerId, c.name AS customerName, c.tier AS tier
""", id=customer_id)
product_rows, t_catalog = run("catalog", """
MATCH (p:Product) WHERE p.productId IN $ids
RETURN p.productId AS productId, p.name AS productName, p.price AS currentPrice
""", ids=product_ids)
products = {p["productId"]: p for p in product_rows}
result = {
"orderId": order_rows[0]["orderId"],
"status": order_rows[0]["status"],
"customer": customer_rows[0],
"lines": [{**r, **products[r["productId"]]} for r in order_rows],
}
print(result)
print({"orders_s": t_orders, "customers_s": t_customers, "catalog_s": t_catalog})
for d in drivers.values(): d.close()
4. Optional Enterprise architecture
If self-managed Enterprise is justified, the same domain stores can be standard databases and exposed through a composite namespace. Keep the architecture diagram honest: the composite is a query/federation layer. Draw Catalog/Orders/Customers as the data-bearing recovery/security units underneath it.
// OPTIONAL: self-managed Neo4j Enterprise 2026.07.1; run administration against system.
CYPHER 25
CREATE DATABASE `atlasmart-catalog` IF NOT EXISTS;
CREATE DATABASE `atlasmart-orders` IF NOT EXISTS;
CREATE DATABASE `atlasmart-customers` IF NOT EXISTS;
CREATE COMPOSITE DATABASE atlasmart IF NOT EXISTS DEFAULT LANGUAGE CYPHER 25;
CREATE ALIAS atlasmart.catalog IF NOT EXISTS FOR DATABASE `atlasmart-catalog`;
CREATE ALIAS atlasmart.orders IF NOT EXISTS FOR DATABASE `atlasmart-orders`;
CREATE ALIAS atlasmart.customers IF NOT EXISTS FOR DATABASE `atlasmart-customers`;
SHOW DATABASE atlasmart YIELD name, type, currentStatus, constituents, defaultLanguage;
SHOW ALIASES FOR DATABASE
YIELD name, composite, database, location, url, credentials, user
WHERE composite = 'atlasmart'
RETURN * ORDER BY name;
CYPHER 25
CALL {
USE atlasmart.orders
MATCH (o:Order {orderId:$orderId})-[:PLACED_BY]->(cr:CustomerRef)
MATCH (o)-[li:CONTAINS]->(pr:ProductRef)
RETURN o.orderId AS orderId, o.status AS status,
cr.customerId AS customerId, pr.productId AS productId,
li.quantity AS quantity, li.unitPrice AS unitPrice
}
CALL {
USE atlasmart.customers
WITH customerId
MATCH (c:Customer {customerId:customerId})
RETURN c.name AS customerName, c.tier AS tier
}
CALL {
USE atlasmart.catalog
WITH productId
MATCH (p:Product {productId:productId})
RETURN p.name AS productName, p.price AS currentPrice
}
RETURN orderId, status, customerId, customerName, tier,
productId, productName, quantity, unitPrice, currentPrice
ORDER BY productId;
5. Failure-domain acceptance test
| Injected condition | Expected application/DB evidence | Required architecture response |
|---|---|---|
| Catalog unavailable | Orders/Customers remain independently reachable; federated product enrichment fails/degrades | define timeout, fallback/read model or endpoint failure contract |
| Customers access denied | composite/application customer lookup denied while other domains may work | least privilege and explicit authorization error path |
| stale ProductRef | reconciliation reports proxy key without live Catalog Product | tombstone/history rule or data-quality incident |
| remote latency spike | constituent/driver timing increases; total p95/p99 rises | budget, cache/read model, locality or SLO change based on evidence |
| mixed-version unsupported feature | query/preflight failure on older constituent | compatibility gate blocks rollout; upgrade or use common feature subset |
| restore one constituent from older point | local data may be valid while cross-domain refs diverge | RPO alignment + reconciliation + business recovery decision |
6. Migration and rollback plan
| Phase | Forward action | Rollback condition / action |
|---|---|---|
| discover | inventory queries/invariants/owners and cross-domain cardinalities | no data move; reject split if dominant atomic traversal crosses proposed boundary |
| introduce IDs/proxies | add stable constrained IDs while graph remains together | remove unused proxy/read paths if contract fails tests |
| dual-read/read model | compare old local traversal versus federated result | route reads back to original graph on mismatch/SLO regression |
| move ownership | cut writes to new owner with idempotent migration and reconciliation | freeze writes and restore previous owner if cutover acceptance fails |
| optional composite | add alias namespace after stores are already correct | clients can target standard databases directly if composite layer causes compatibility/operational issue |
| retire old copy | only after reconciliation/backup/rollback window closes | retain backup/export/checkpoint per governance policy |
7. Cost model
| Cost | Single graph | Multi-domain/federated |
|---|---|---|
| graph traversal | best locality for connected traversal | some traversals become joins/remote calls |
| write atomicity | strong within one database | cross-domain writes require ownership redesign/asynchrony |
| security/data lifecycle | coarser shared boundary unless fine-grained Enterprise controls | stronger domain separation possible |
| operations | one store/deployment simpler | more backups, upgrades, monitoring, secrets, compatibility and incident paths |
| capacity | one shared scaling unit | domains can scale/locate independently, but federation/network cost grows |
| licensing | Community single DB may suffice | Enterprise composites/multi-DB may add commercial cost; separate Community instances add operator cost |
8. Final verification and cleanup
Before cleanup, rerun the fixture verification and O-1901 federation probe. Preserve only the architecture decision record and measured evidence—not the synthetic password or disposable stores.
CYPHER 25
// Run on catalog
MATCH (p:Product {labTag:'ch19'}) RETURN count(p) AS products;
// Expected: 3
// Run on customers
MATCH (c:Customer {labTag:'ch19'}) RETURN count(c) AS customers;
// Expected: 3
// Run on orders
MATCH (o:Order {labTag:'ch19'}) RETURN count(o) AS orders;
MATCH (:Order {labTag:'ch19'})-[r:CONTAINS]->(:ProductRef) RETURN count(r) AS lines;
// Expected: orders=3, lines=4
# Remove disposable Chapter 19 Community instances and their data volumes.
docker rm -f atlasmart-ch19-catalog atlasmart-ch19-orders atlasmart-ch19-customers
docker volume rm atlasmart-ch19-catalog-data atlasmart-ch19-orders-data atlasmart-ch19-customers-data
docker network rm atlasmart-ch19-net
Check your understanding
- When should an edge remain local?
- What does Enterprise composite add to the free three-instance architecture?
- What is the fundamental cross-graph consistency boundary?
- Why is backing up every constituent necessary?
- What is a valid reason to reject Chapter 19 federation?
Review the answers
1. When its traversal/invariant/atomicity is owned together and federation would damage correctness or dominant workload performance.
2. A logical federation/query namespace using constituent aliases and USE/CALL; it does not move or merge the underlying stores.
3. No relationship crosses graphs and one composite transaction can update only one constituent; other coordination needs application/asynchronous patterns.
4. They contain the actual graph data; the composite alias layer alone cannot recover domain stores.
5. Evidence that the split breaks required atomic invariants, makes dominant traversal/tail latency unacceptable, or creates operational/security cost greater than its ownership/scaling benefits.
Production judgment
| Decision surface | Production questions |
|---|---|
| graph/workload fit | Does domain separation reduce ownership/capacity coupling, or does it turn the dominant traversal into repeated remote joins? |
| correctness | Which invariants remain atomic inside one graph, and which become asynchronous/application-coordinated across domains? |
| model/cardinality/degree | Which high-degree relationships must remain local for traversal cost and invariant enforcement? Which references are safe as proxy keys? |
| latency | How much p95/p99 is local graph work versus remote constituent/network/application join time? What happens during remote tail spikes? |
| transactions/concurrency | Which writes must commit together? Composite transactions may read many graphs but update only one constituent; plan compensating/outbox workflows elsewhere. |
| memory/resources | Do multiple databases share a DBMS resource envelope? Do separate instances need independent memory/page-cache/process budgets and capacity headroom? |
| CPU/disk/network | Does federation move filtering to constituents or ship excessive rows across network boundaries? Are region/zone egress and serialization material? |
| indexes/constraints | Are stable IDs constrained and indexed independently on each owning/proxy graph? No cross-graph relationship or uniqueness constraint exists. |
| driver | Are database/alias names explicit, drivers long-lived, timeouts/retries bounded, and per-domain timings correlated? |
| security/tenant risk | Are ACCESS/MATCH/procedure rights granted on every required constituent? How are remote credentials/OIDC forwarding, TLS and tenant boundaries governed? |
| backup/recovery | Can every constituent be restored and reconciled independently? A composite alias layer is not a backup of its target stores. |
| observability | Can operators attribute latency/errors to the root composite scope and each local/remote constituent without hiding network waits? |
| testing/failure injection | Have one-domain-down, stale proxy, version mismatch, denied constituent, slow remote graph and ambiguous cross-service write cases been tested safely? |
| version/edition/Aura | Is the design pinned to self-managed Enterprise composite support? What is the fallback for Community/Aura or mixed-version constituents? |
| licensing/cost/migration | Does federation justify Enterprise/ops/network cost, and can the split be rolled back or re-partitioned without breaking identity contracts? |
Chapter 19 completion checklist
| Evidence | Pass condition |
|---|---|
| domain ownership | Catalog/Customers/Orders owners and write invariants documented |
| identity | stable constrained productId/customerId/orderId; proxy semantics explicit |
| free lab | three Community instances verified; application federation returns deterministic O-1901 result |
| Enterprise option | composite commands clearly labeled optional and not Aura/Community |
| query correctness | local filtering/batching; no cross-graph relationship assumptions; no two-graph atomic writes |
| security | constituent/remote access and credential/TLS ownership documented |
| observability | per-constituent + total latency/errors visible |
| recovery | each constituent backup/restore + cross-domain reconciliation defined |
| version | composite/constituent/driver/Cypher compatibility gate defined |
| rollback | cutover can route back/reconcile within explicit rollback window |
Summary and next step
Chapter 19 turns multi-database federation into a domain architecture discipline: data stays in owned constituent stores, identities cross boundaries as explicit values, federation is measured, and security/recovery/versioning remain per-domain responsibilities. Chapter 20 can now build change-data-capture and event integration on top of these ownership boundaries without confusing CDC with cross-database atomicity.
Authoritative references
- Current Neo4j versions — Current self-managed release 2026.07.1 and 5.26.30 LTS snapshot.
- Database administration — Current database/transaction-domain model and the one-standard-database Community versus multi-database Enterprise boundary.
- Create standard databases — Enterprise-only self-managed CREATE DATABASE semantics and system-database administration.
- Show databases — SHOW DATABASES fields including type, role, writer, status, aliases and constituents.
- Composite database concepts — Enterprise-only, unavailable-on-Aura composite semantics, local/remote constituents, compatibility, transactions and proxy-node federation.
- Create composite databases — CREATE COMPOSITE DATABASE and current default Cypher language behavior.
- Query composite databases — USE/CALL graph selection, graph functions, update restrictions, root-scope limits and runtime behavior.
- Composite aliases — Local and remote constituent aliases, SHOW ALIASES evidence, namespace rules and alias limitations.
- Standard aliases — Local/remote alias behavior, credentials, access visibility and current OIDC-forwarding option for remote aliases.
- Composite RBAC — Access must be granted to the composite and constituents; remote constituents enforce remote-user RBAC.
- Composite tutorial — Official federation/sharding example and proxy-node model.
- Database alias command syntax — Current SHOW/CREATE/ALTER alias command forms.
- Cypher USE clause — Current graph-selection clause semantics for composite/federated queries.
- Python driver manual — Official driver lifecycle, sessions, parameters, database selection and application-side orchestration.
- Backup and restore — Operational reminder that constituent data stores—not a composite alias layer—are the recovery units.