iolite/Nebula

Relationship Patterns

In Nebula, relationships between entities aren't just structural — they're queryable state. Links (parent and child) are first-class citizens in the query language, which means creating a relationship between two entities can trigger systems, filter UI views, and drive business logic. This pattern explores how to use links to model meaningful, multi-dimensional relationships.

The Scenario

Consider a healthcare denial management application. An Account entity represents a patient's denied insurance claim group. But an Account doesn't exist in isolation — it needs to connect to the Team working it, the facility Location where services were rendered, and the individual Claims that make up the denial. Each relationship serves a different purpose and different systems care about different links.

The Pattern

Instead of storing foreign keys in component fields, model each relationship as a Nebula link. The Account becomes a central hub with parent links going up to Team and Location, and child links going down to Claims.

Account+ Customer + DenialOpportunityPARENT LINKTeamPARENT LINKLocationCHILDClaim #1CHILDClaim #2CHILDClaim #3Parent link (up)Child link (down)Central entity

The key insight: each link type is independently queryable. Systems can trigger on link changes, and queries can filter by relationship existence.

TeamAssignmentSystem

and
hasAccount
hasCustomer
hasDenialOpportunity
notno Team parent
parent
hasTeam
hasStandalone

When matched, the system calls ctx.linkTo(teamId) to create a parent link from the Account to the assigned Team entity.

Notice the guard: not(parent(has('Team'))) prevents the system from re-running if the Account is already assigned. The absence of a link is the trigger condition, and creating the link is what resolves it.

In Practice

When EDI data is ingested, Claim entities are created and linked as children of the Account. Three independent systems then react to the Account's evolving state:

  1. LocationMatching queries not(has('LocationMatch')) + child(has('Claim')) — uses tax IDs from child Claims to find and link to the correct Location entity
  2. TeamAssignment queries received('DenialOpportunity') + no Team parent — assigns the Account to a work team via a parent link
  3. Frontend queries like parent(has('Team')) + has('DenialPriority') filter the work queue to show only assigned, scored accounts

Each link is created by a different system at a different time, but they all contribute to the Account's queryable relationship graph.

Why This Works

In a traditional architecture, you'd maintain foreign key columns, join tables, and event handlers to keep relationships in sync. Each new relationship type means new migration scripts, new API endpoints, and new subscription logic.

In Nebula, a link is a link. Creating one is a single linkTo() call, querying across it uses the same operators as everything else, and any link change can trigger downstream systems automatically. The relationship graph grows organically as systems add links, and every new connection is immediately queryable.


Behaviors covers how systems declare interest in entity state. Component Query Refiners explores how component presence drives system routing. Async Workflows shows how these relationships combine into complex pipelines.