Chapter 02 · Property Graph Model: Nodes, Relationships, Labels, Properties, Types, and Schema Discipline
Model Identity, Natural Keys, Surrogate Identifiers, Relationship Properties, and Effective Dating
Design identity and time explicitly so graph references survive infrastructure change and historical relationships remain queryable without accidental overwrite.
Learning outcomes
Identity is not a single Neo4j feature. AtlasMart has natural
identifiers such as SKU or supplier registration number,
application-generated surrogate IDs such as P-1001,
Neo4j implementation element IDs, and relationships whose
properties may change over time. Choosing the wrong identity
surface makes restores, merges, CDC, imports, and temporal
queries brittle.
Compare natural keys, application surrogate identifiers, composite business identity, and Neo4j element IDs.
Choose stable application-generated IDs backed by uniqueness constraints for long-lived graph references.
Place facts on relationships only when they describe the connection rather than one endpoint.
Model effective dating with explicit validity intervals and define overlap/current-state invariants.
Recognize when a relationship should be reified as a node because it has independent lifecycle or multi-party semantics.
Continue Chapter 01's free/local baseline: Neo4j Community
2026.07.1, database neo4j, explicit
CYPHER 25 in version-sensitive examples, local
container atlasmart-neo4j, Bolt
127.0.0.1:7687, HTTP 127.0.0.1:7474,
and disposable password atlasmart-course-2026.
Existing AtlasMart identifiers use Community-supported
uniqueness constraints. This chapter adds
:Category, :Supplier,
:Store, and :Shipment identities and
makes graph-model contracts observable.
The generation environment does not provide a running Docker daemon or Neo4j server, so commands are documentation-checked but not presented as captured output. Community supports the uniqueness constraints used in the mandatory lab. Current property-existence, property-type, and key constraints are Enterprise-only; when those stronger contracts are discussed, the Community path uses validation queries plus application/import checks rather than fabricating successful Community DDL.
Business identity must survive infrastructure change
A natural key comes from the business domain:
an ISO code, immutable external registration number, or perhaps
a SKU if the organization truly guarantees its stability. A
surrogate ID is created by the application
specifically to identify the entity independently of descriptive
attributes. Neo4j's elementId() is neither: it is
an implementation-facing identifier whose mapping is not
guaranteed as a durable application identity across
transactions/environments.
| Identity choice | Strength | Risk |
|---|---|---|
| Natural key | Human/domain meaningful and may already exist across systems | Can change, be recycled, be composite, or have messy source-system semantics |
| Application surrogate ID | Stable across copy/import/restore if preserved; simple relationship/reference key | Needs generation/governance and source-system mapping |
| Composite business key | Can accurately express tenant + external ID or other scoped identity | More fields in every lookup and migration; changes are harder |
| elementId() | Convenient for in-transaction implementation evidence | Not a durable business key; mapping guarantees are intentionally limited |
AtlasMart keeps customerId, productId,
orderId, and similar application IDs as its graph
identity surface. If a source ERP supplier has
registrationNumber, AtlasMart can store it and
possibly constrain it when the business truly guarantees
uniqueness, but it should not silently replace the internal
integration ID until lifecycle rules are known.
Relationship properties belong to the connection
STOCKED_AT.onHand is about a product at a specific
store; it does not belong solely to the product or the store.
CONTAINS.quantity is about one product's
participation in one order.
SUPPLIED_BY.preferred describes AtlasMart's
product-supplier connection. Those are good
relationship-property candidates because the value changes when
either endpoint changes.
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(st:Store {storeId:'ST-TEH-01'})MERGE (p)-[stock:STOCKED_AT]->(st)SET stock.onHand=14, stock.reorderPoint=5, stock.observedAt=datetime('2026-09-09T10:00:00Z');CYPHER 25MATCH (p:Product {productId:'P-1001'})-[stock:STOCKED_AT]->(st:Store)RETURN p.productId, st.storeId, stock.onHand, stock.reorderPoint, stock.observedAt;
A counterexample is Product.name: copying it onto
every STOCKED_AT relationship creates write
amplification and conflicting copies with no
relationship-specific meaning. Denormalization can still be
justified for measured queries, but it must be owned explicitly
as a derived projection rather than smuggled into the model.
Effective dating makes “current” a query, not an overwrite
Some relationships are true only during an interval. Supplier
contracts, store assignments, pricing agreements, or category
classifications can change. Overwriting one edge property
destroys history. Effective dating keeps separate facts with
validFrom and optional validTo, but
then AtlasMart must define whether intervals may overlap and how
open-ended current state is represented.
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(s:Supplier {supplierId:'SUP-1001'})CREATE (p)-[:SUPPLIED_BY { agreementId:'AGR-2026-001', validFrom:date('2026-01-01'), validTo:date('2026-06-30'), unitCost:91.00}]->(s)CREATE (p)-[:SUPPLIED_BY { agreementId:'AGR-2026-002', validFrom:date('2026-07-01'), unitCost:94.50}]->(s);
This intentionally creates separate historical relationships
rather than MERGEing one generic product→supplier
edge. The identity now includes agreement semantics. If
agreements are referenced by invoices, approvals, documents, or
more than two parties, model
:SupplyAgreement {agreementId} as a node and
connect participants to it. Reification is justified by
lifecycle/query needs, not by a rule that every relationship
with properties must become a node.
CYPHER 25MATCH (p:Product {productId:$productId})-[r:SUPPLIED_BY]->(s:Supplier)WHERE r.validFrom <= $asOf AND (r.validTo IS NULL OR r.validTo > $asOf)RETURN s.supplierId, r.agreementId, r.unitCost, r.validFrom, r.validToORDER BY r.validFrom DESC;
Define interval boundaries precisely. The query above uses a
half-open interpretation for validTo (validFrom <= asOf < validTo), while an open-ended missing validTo means still
valid. Another system may use inclusive end dates. The important
part is one documented convention, not the specific choice.
Deliberately break temporal identity with overlapping active contracts
CYPHER 25MATCH (p:Product {productId:'P-1001'}),(s:Supplier {supplierId:'SUP-1001'})CREATE (p)-[:SUPPLIED_BY { agreementId:'AGR-BAD-OVERLAP', validFrom:date('2026-08-01'), unitCost:89.00}]->(s);
If AtlasMart's contract says only one active agreement may exist for a product-supplier pair, this write violates the model even though each relationship is structurally valid. Detect it by testing an as-of date and counting active relationships:
CYPHER 25WITH date('2026-09-09') AS asOfMATCH (p:Product)-[r:SUPPLIED_BY]->(s:Supplier)WHERE r.validFrom <= asOf AND (r.validTo IS NULL OR r.validTo > asOf)WITH p, s, collect(r.agreementId) AS activeAgreementsWHERE size(activeAgreements) > 1RETURN p.productId, s.supplierId, activeAgreements;
The database cannot infer which contract is legitimate. The safe
repair is business reconciliation, not deleting whichever edge
is newest. In this synthetic lab, remove only
AGR-BAD-OVERLAP after proving it is the injected
record:
CYPHER 25MATCH ()-[r:SUPPLIED_BY {agreementId:'AGR-BAD-OVERLAP'}]->()DELETE r;
Natural-key change and alias strategy
Suppose supplier registration numbers can change after corporate
restructuring. If registrationNumber is the only
node identity, changing it can break external references and
deduplication history. AtlasMart's stable
supplierId remains the node key while registration
numbers become attributes, perhaps with an
:ExternalIdentifier node or history structure if
aliases must be searchable over time.
The production decision surface includes collision handling,
source precedence, mergers/splits, tenant scoping, GDPR/privacy
constraints on identifiers, and whether an identifier can be
regenerated after backup/restore. Identity policy belongs in
architecture documentation and tests, not only in one
CREATE CONSTRAINT statement.
Lab: defend each identity and temporal choice
CYPHER 25MATCH (n)WHERE any(k IN keys(n) WHERE k ENDS WITH 'Id')RETURN labels(n) AS labels, [k IN keys(n) WHERE k ENDS WITH 'Id' | k] AS idKeys, count(*) AS nodesORDER BY labels;CYPHER 25MATCH ()-[r]->()WHERE size(keys(r)) > 0RETURN type(r) AS relationshipType, keys(r) AS propertyKeys, count(*) AS relationshipsORDER BY relationshipType, propertyKeys;
Verification checklist: every durable entity has an
application/domain ID; no application contract stores
elementId(); relationship properties describe the
connection; effective-dated intervals use one boundary
convention; overlap rules are testable; and reified relationship
nodes are introduced only when independent
identity/lifecycle/multi-party traversal justifies them.
Check your understanding
- Why is elementId() not a replacement for supplierId?
- Give one fact that naturally belongs on STOCKED_AT.
- When does a relationship with properties deserve reification as a node?
- Why must validTo boundary semantics be documented?
- Why is an overlapping agreement not automatically repairable by “keep newest”?
Review the answers
1. supplierId is an application-controlled identity intended to survive system movement; elementId mapping is not guaranteed as a durable cross-transaction/environment key.
2. onHand, reorderPoint, observedAt, or another fact specific to that product-store pair.
3. When the connection has independent durable identity/lifecycle, many participants, references from other facts, rich history, or query needs that make it an entity.
4. Inclusive/exclusive endpoints change which relationship is current at boundary instants/dates.
5. The database does not know business authority; timestamps/order alone do not prove which contract is valid. Reconciliation must use source/business evidence.
Summary and next step
AtlasMart now separates business identity from implementation IDs, locates connection-specific facts on relationships, and treats temporal validity as explicit data with testable interval semantics. The final lesson uses all of Chapter 02 to translate relational/document source shapes into a graph and defend each modeling decision against a concrete query.
Authoritative references
- Current Neo4j versions — Official current-release and 5.26 LTS patch snapshot.
- Cypher Manual — Current Cypher language reference.
- Property, structural, and constructed values — Current property/storage type rules, including lists and maps.
- Working with null — Current missing-property and three-valued null semantics.
- Create constraints — Constraint syntax, integrity semantics, backing indexes, and edition boundaries.
- Scalar functions: elementId() — Current elementId() guarantees and warning against durable application identity.
- MATCH — Directed and undirected relationship-pattern matching semantics.
- CREATE — Node and relationship creation semantics.
- Naming rules and recommendations — Current naming recommendations and case sensitivity.