Skip to content

Cephalon.Data.Neo4j

Maturity: M1 · Ownership: provider-managed · Family: data-and-cdc · See audit, matrix.

Cephalon.Data.Neo4j is the Neo4j graph-store companion pack for Cephalon, proving that the companion-pack pattern established by Cephalon.Data.MongoDB and Cephalon.Data.Redis extends cleanly to graph-oriented databases without any changes to Cephalon.Engine or Cephalon.Abstractions.

  • registers a singleton IDriver from a Bolt URI and credentials using TryAdd semantics so a host-owned driver is never displaced
  • registers a scoped IOutbox backed by Neo4j :OutboxMessage graph nodes when RegisterOutbox is enabled; node labels honour the optional LabelPrefix
  • registers a scoped IEventDispatchStore against the same Neo4j outbox nodes when RegisterOutbox is enabled so consumer-managed or adapter-managed dispatch can read pending staged messages and persist durable dispatch outcomes without a relational bridge
  • registers a scoped IInbox backed by Neo4j :InboxReceipt graph nodes when RegisterInbox is enabled; node labels honour the optional LabelPrefix
  • ensures that outbox staging is idempotent via Cypher MERGE on messageId — the ON CREATE SET clause fires only for new nodes, making repeated EnqueueAsync calls with the same message id safe no-ops
  • ensures that inbox tracking is idempotent via the same MERGE pattern on messageId
  • bootstraps a uniqueness constraint on each managed label on first use (via CREATE CONSTRAINT ... IF NOT EXISTS)
  • exposes operator-facing outbox and inbox descriptors through the engine runtime surfaces
  • projects the outbox descriptor through the event-driven-integration technology surface as outbox producers when that technology is active
  • projects the inbox descriptor through the same technology surface as inbox stores when the technology is active
  • publishes capability metadata data.neo4j, data.graph-store, and optionally data.outbox.neo4j and data.inbox.neo4j introspectable at runtime through the manifest
  • Configuration/Neo4jDataOptions.cs
  • Modules/Neo4jDataModule.cs
  • Registration/Neo4jDataEngineBuilderExtensions.cs
  • Services/Neo4jOutbox.cs
  • Services/Neo4jEventDispatchStore.cs
  • Services/Neo4jOutboxRuntimeSurfaceContributor.cs
  • Services/Neo4jInbox.cs
  • Services/Neo4jInboxRuntimeSurfaceContributor.cs

This pack sits on top of Cephalon.Data, not in place of it. Cephalon.Data still owns the runtime-neutral IReadStore / IWriteStore dispatching surface. Cephalon.Data.Neo4j adds the Neo4j-backed outbox and inbox persistence paths that let event-driven workloads stage and track messages against a graph store without switching to a relational or document-oriented provider.

The slice is intentionally narrow: it proves the companion-pack pattern works for graph-oriented stores, ships idempotent outbox and inbox implementations, adds a consumer-managed dispatch-store baseline for staged outboxes, and exposes the same runtime introspection surfaces as the MongoDB and Redis providers.

engine.AddNeo4jData(
uri: "bolt://localhost:7687",
username: "neo4j",
password: "secret");

To enable the outbox and inbox paths:

engine.AddNeo4jData(
uri: "bolt://localhost:7687",
username: "neo4j",
password: "secret",
configure: options =>
{
options.RegisterOutbox = true;
options.RegisterInbox = true;
options.LabelPrefix = "MyApp"; // optional — prefix all Cephalon node labels
});

To resolve the Bolt URI from the root Uris section:

engine.AddNeo4jData(options =>
{
options.UriName = "Graph";
options.Username = "neo4j";
options.Password = "secret";
options.RegisterOutbox = true;
});
{
"Uris": {
"Graph": "bolt://localhost:7687"
},
"Engine": {
"Data": {
"Neo4j": {
"UriName": "Graph",
"Username": "neo4j",
"Password": "secret"
}
}
}
}

UriName and Uri are mutually exclusive. If both are set, the pack throws during service registration. Leaving both unset falls back to bolt://localhost:7687.

OptionTypeDefaultDescription
UriNamestring?nullRoot Uris key to resolve for Neo4j
Uristring?nullInline Neo4j Bolt URI
Usernamestring"neo4j"Neo4j username
Passwordstring""Neo4j password
LabelPrefixstring"Cephalon"Prefix applied to all managed node labels
RegisterOutboxboolfalseRegister IOutbox backed by :{LabelPrefix}OutboxMessage nodes
RegisterInboxboolfalseRegister IInbox backed by :{LabelPrefix}InboxReceipt nodes

OutboxMessage node schema (:{LabelPrefix}OutboxMessage)

Section titled “OutboxMessage node schema (:{LabelPrefix}OutboxMessage)”

The node label is {LabelPrefix}OutboxMessage (default: :CephalonOutboxMessage).

PropertyTypeNotes
messageIdstringUnique idempotency key; uniqueness constraint prevents duplicate staging
channelIdstringTarget channel identifier
messageTypestringFully qualified CLR message type name
payloadstringSystem.Text.Json-serialized message body
contentTypestringMIME content type (empty string when not provided)
correlationIdstringOptional causality tracking identifier
tenantIdstringOptional multi-tenancy discriminator
occurredAtUtcstringISO 8601 UTC timestamp when the domain event occurred
createdAtUtcstringISO 8601 UTC timestamp when the message was staged
dispatchAttemptCountintInitialized to 0; incremented by dispatch adapters
dispatchedAtUtcstringOptional ISO 8601 UTC timestamp written when dispatch succeeds or is skipped
nextAttemptAtUtcstringOptional ISO 8601 UTC timestamp written when dispatch is delayed for retry
headersJsonstringSystem.Text.Json-serialized headers dictionary
metadataJsonstringSystem.Text.Json-serialized metadata dictionary

InboxReceipt node schema (:{LabelPrefix}InboxReceipt)

Section titled “InboxReceipt node schema (:{LabelPrefix}InboxReceipt)”

The node label is {LabelPrefix}InboxReceipt (default: :CephalonInboxReceipt).

PropertyTypeNotes
messageIdstringProcessed message id; uniqueness constraint enforces exactly-once semantics
channelIdstringChannel the message arrived on
messageTypestringFully qualified CLR message type name
correlationIdstringOptional causality tracking identifier
tenantIdstringOptional multi-tenancy discriminator
receivedAtUtcstringISO 8601 UTC timestamp when the message was received
processedAtUtcstringISO 8601 UTC timestamp when the message was first processed

Both IOutbox.EnqueueAsync and IInbox.MarkProcessedAsync use Cypher MERGE on messageId:

MERGE (m:CephalonOutboxMessage {messageId: $messageId})
ON CREATE SET m.channelId = $channelId, ...

ON CREATE SET fires only when the MERGE creates a new node. When the node already exists (i.e., the message was already staged or processed), MERGE matches it and the ON CREATE SET block is skipped — making repeated calls with the same id safe no-ops. No exception is thrown and no special error code handling is required.

A uniqueness constraint is created on first use via CREATE CONSTRAINT ... IF NOT EXISTS, ensuring the MERGE key is indexed and enforced at the database level.

When Neo4jDataModule is active, the following capability keys appear in the runtime manifest:

Capability keyWhen registered
data.neo4jAlways
data.graph-storeAlways
data.outbox.neo4jRegisterOutbox = true
data.inbox.neo4jRegisterInbox = true

When the event-driven-integration technology is active, the following entries appear under /engine/snapshot:

SurfaceEntry idprovider metadata
outbox-producersneo4j-outboxneo4j
inbox-storesneo4j-inboxneo4j

This pack intentionally does not claim:

  • graph traversal queries or relationship modeling between domain entities
  • full-text search or spatial index queries
  • APOC procedure integration
  • IReadStore / IWriteStore dispatch backed by Neo4j — query and command handlers should use IDriver directly
  • transaction-scoped outbox staging spanning multiple graph operations
  • adapter-owned dispatch loops or broker retry scheduling beyond the shipped consumer-managed dispatch-store baseline
  • change-data-capture or Neo4j streams integration

These remain explicit later slices to keep the initial provider claim honest.