iolite/Nebula

Data-First Design

In Nebula, data is the application. Instead of building services around business logic and hoping the data layer keeps up, you model your entire domain as composable components on lightweight entities. The data model drives everything — storage, processing, real-time updates, and access control.

Entities and Components

An entity in Nebula is deliberately simple: an ID, creation metadata, and an owner. That's it. All meaningful data lives in components — typed JSON objects attached directly to the entity's database record.

This means no joins. No lookup tables. When you query an entity, you get everything it carries in a single read.

Each entity can have at most one instance of any given component type. This single-component dimensionality keeps the data model flat and predictable. If an Account entity has a Customer component, there's exactly one — not a list, not a nullable reference to another table.

Patient

firstName
string
lastName
string
dateOfBirth
string
memberId
string?Insurance member identifier
required

Claim

status
string
totalChargeAmount
number
periodStart
string
submitterId
string?Original claim submission ID
required

The Relationship Graph

Since each entity can only have one instance of each component, Nebula supports a relationship graph to model more complex structures. Any entity can have multiple parents and multiple children. These relationships are semantic — it's up to you to decide what parent/child means in your domain.

An Account entity might have Claim entities as children and be a child of an Organization entity. A Claim might link to both an Account (parent) and multiple ServiceLine entities (children). The graph is flexible and doesn't impose hierarchy.

This is fundamentally different from relational foreign keys. Relationships in Nebula are first-class: you can query by them, subscribe to changes in them, and use them in access control rules.

OrganizationOrganizationparentAccountCustomerDenialOpportunityAppealTimelyFilingchildchildClaimClaimInsuranceClaimClaimServiceLine

Why This Matters

Traditional applications accumulate complexity through schema migrations, ORM configurations, and ever-growing join queries. Adding a new data dimension means altering tables, updating models, and hoping nothing breaks downstream.

In Nebula, adding new data is additive by nature. Want to track denial opportunities on existing Account entities? Register a DenialOpportunity component and start attaching it. No migration. No schema change. No existing code breaks — components that don't know about DenialOpportunity simply ignore it.

This composability extends to queries. Because components live directly on entities, you can query across component boundaries without joins:

Find denial accounts with timely filing data

and
hasAccount
hasCustomer
hasDenialOpportunity
hasAppealTimelyFiling

No joins. No repository patterns. The query describes the composition you're interested in, and Nebula returns matching entities with all their components.


The data-first approach means your Nebula application grows by adding components — not by redesigning tables or refactoring services. This is the foundation that Behaviors and Orchestration build on. See Relationship Patterns for how links extend this model into queryable entity graphs.