Skip to content

feat(datamodeling): Avro Contracts and Kafka lifecycle events#101

Merged
phuongnse merged 3 commits into
mainfrom
cursor/datamodeling-contracts-retrofit-e85a
May 24, 2026
Merged

feat(datamodeling): Avro Contracts and Kafka lifecycle events#101
phuongnse merged 3 commits into
mainfrom
cursor/datamodeling-contracts-retrofit-e85a

Conversation

@phuongnse

@phuongnse phuongnse commented May 24, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the DataModeling Phase 2 service-boundary retrofit: new Axis.DataModeling.Contracts project with nine Avro event schemas published via Wolverine outbox → Kafka (CloudEvents, ADR-019). Domain field mutations now raise FieldAdded, FieldUpdated, and FieldRemoved events; DataModelingEventMapper replaces raw domain-event publishing in DataModelingUnitOfWork. Updates PROGRESS.md to reflect that WorkflowBuilder/FormBuilder/WorkflowEngine contract and per-module DB work was already in place.

Linked spec

Requirements

  • Axis.DataModeling.Contracts with Avro schemas + generated records
  • Kafka topics registered in Program.cs (publish + local testing)
  • scripts/register-avro-schemas.sh extended for DataModeling subjects
  • Domain + mapper tests; architecture tests pass
  • dotnet build and dotnet format --verify-no-changes green
  • Gate 2: PROGRESS.md and epics README updated

Deferred (follow-up PR): Kafka consumers in FormBuilder/WorkflowBuilder for model/field delete (broken-reference detection); DataModeling gRPC; WorkflowEngine IScriptExecutor/INotificationSender; E01 provisioning retry/UI.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • DataModeling now publishes Avro lifecycle events (models, data classes, records, fields) via outbox→Kafka; domain changes emit field-created/updated/removed events.
  • Documentation
    • Project progress and epic docs updated to mark DataModeling Phase 2 complete and note deferred cross-module consumers/gRPC follow-ups.
  • Tests
    • Added unit tests for event mapping and domain event emission; architecture tests updated to include the contracts assembly.
  • Chores
    • Schema registration script extended to register new DataModeling Avro schemas.

Review Change Stack

- Axis.DataModeling.Contracts (9 lifecycle + field events)
- DataModelingEventMapper and domain FieldAdded/Updated/Removed
- Wolverine Kafka wiring in Axis.Api Program.cs
- Update PROGRESS and register-avro-schemas.sh

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2faadf87-a156-4693-bb30-fd620a6d76fd

📥 Commits

Reviewing files that changed from the base of the PR and between 2a3f275 and b0e2669.

📒 Files selected for processing (2)
  • src/Modules/DataModeling/Axis.DataModeling.Domain/Aggregates/DataModel.cs
  • tests/Modules/DataModeling/Axis.DataModeling.Domain.Tests/DataModelTests.cs

📝 Walkthrough

Walkthrough

This PR completes DataModeling Phase 2 by adding Axis.DataModeling.Contracts (Avro schemas, topic constants, GUID-parsing extensions), domain events and emissions for field lifecycle, mapping domain events to Avro integration events for outbox publication, Kafka wiring, schema registration updates, docs, and tests.

Changes

DataModeling Event Contracts and Publishing

Layer / File(s) Summary
Avro Event Contracts and Routing
Axis.sln, src/Modules/DataModeling/Axis.DataModeling.Contracts/Axis.DataModeling.Contracts.csproj, src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/*, src/Modules/DataModeling/Axis.DataModeling.Contracts/DataModelingKafkaTopics.cs, src/Modules/DataModeling/Axis.DataModeling.Contracts/DataModelingEventExtensions.cs
Nine Avro schema files define Model/DataClass/DataRecord/Field lifecycle events in namespace axis.datamodeling.events. DataModelingKafkaTopics defines topic constants. DataModelingEventExtensions exposes GUID accessors parsing Avro string fields via a shared helper that throws on invalid GUIDs.
Domain Event Types and Emission
src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldAdded.cs, src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldUpdated.cs, src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldRemoved.cs, src/Modules/DataModeling/Axis.DataModeling.Domain/Aggregates/DataModel.cs
New sealed records FieldAdded, FieldUpdated, FieldRemoved implement IDomainEvent. DataModel.AddField, UpdateField, and RemoveField now raise the corresponding domain events after state changes.
Integration Event Mapping and Kafka Wiring
src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Messaging/DataModelingEventMapper.cs, src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Axis.DataModeling.Infrastructure.csproj, src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Persistence/DataModelingUnitOfWork.cs, src/Axis.Api/Program.cs, tests/Modules/DataModeling/Axis.DataModeling.Infrastructure.Tests/Messaging/DataModelingEventMapperTests.cs
DataModelingEventMapper.ToIntegrationEvent converts domain events into Avro contract objects (stringifying GUIDs) and returns null for unknown types. DataModelingUnitOfWork publishes mapped integration events only when non-null. Program.cs registers DataModeling event types for Kafka publish/listen and local publishing. Mapper tests cover mappings and unknown-event behavior.
Schema Registry and Documentation
scripts/register-avro-schemas.sh, docs/PROGRESS.md, docs/epics/README.md, docs/epics/E03-data-modeling/README.md, docs/epics/E03-data-modeling/features/F01-model-definition.md, tests/Architecture/Axis.Architecture.Tests/Axis.Architecture.Tests.csproj, tests/Modules/DataModeling/Axis.DataModeling.Domain.Tests/DataModelTests.cs
Schema registration script adds DataModeling schema registrations and uses WB_SCHEMA_DIR/DM_SCHEMA_DIR. Docs mark Phase 2 contracts shipped and note deferred consumers/gRPC. Architecture tests add project reference to include contracts assembly. Domain tests added/updated to assert FieldAdded/FieldUpdated/FieldRemoved events are raised.

Sequence Diagram(s)

sequenceDiagram
  participant DataModel as DataModelAggregate
  participant UnitOfWork as DataModelingUnitOfWork
  participant Mapper as DataModelingEventMapper
  participant Bus as WolverineBus
  participant Kafka as Kafka
  DataModel->>DataModel: RaiseDomainEvent(FieldAdded)
  UnitOfWork->>UnitOfWork: SaveChangesAsync()
  UnitOfWork->>Mapper: ToIntegrationEvent(FieldAdded)
  Mapper-->>UnitOfWork: FieldAddedEvent(obj) or null
  UnitOfWork->>Bus: PublishAsync(integrationEvent) when not null
  Bus->>Kafka: Send to DataModelingKafkaTopics.FieldAdded
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • phuong-labs/axis#91: Implements the same ADR-019 Avro/Schema-Registry integration pattern for WorkflowBuilder and updates schema registration and Kafka wiring.
  • phuong-labs/axis#87: Modifies UnitOfWork publish flow; this PR builds on that by mapping domain events to integration events before publishing.
  • phuong-labs/axis#84: Configures Wolverine Kafka transport; this PR adds DataModeling-specific event registrations on top of that transport.

🐰 A Contracts Layer Flourishes
Nine schemas bloom, events take flight,
Domain to Kafka, stringified right,
FormBuilder watching, awaiting the feed,
Of models deleted—cross-module seed!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(datamodeling): Avro Contracts and Kafka lifecycle events' clearly and concisely describes the main change: adding Avro event contracts and Kafka lifecycle event support to the DataModeling module.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/datamodeling-contracts-retrofit-e85a

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@phuongnse phuongnse marked this pull request as ready for review May 24, 2026 13:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/register-avro-schemas.sh (1)

8-8: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Define WB_SCHEMA_DIR and DM_SCHEMA_DIR before use.

With set -u, the first register "$WB_SCHEMA_DIR/... call will abort with an unbound variable error.

Suggested fix
-SCHEMA_DIR="$ROOT/src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Schemas"
+WB_SCHEMA_DIR="$ROOT/src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Schemas"
+DM_SCHEMA_DIR="$ROOT/src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas"

Also applies to: 26-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/register-avro-schemas.sh` at line 8, Define and export the
WB_SCHEMA_DIR and DM_SCHEMA_DIR variables before any use to avoid unbound
variable errors under set -u; initialize them (e.g.,
WB_SCHEMA_DIR="$ROOT/src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Schemas"
and DM_SCHEMA_DIR="$ROOT/…/DesiredModule/…/Schemas") near the top of the script,
then use those variables in the register calls instead of relying on SCHEMA_DIR
being present, ensuring any register "$WB_SCHEMA_DIR/..." and register
"$DM_SCHEMA_DIR/..." invocations reference existing variables.
🧹 Nitpick comments (1)
tests/Modules/DataModeling/Axis.DataModeling.Infrastructure.Tests/Messaging/DataModelingEventMapperTests.cs (1)

51-51: ⚡ Quick win

Replace the fully-qualified interface type with a using import.

Use IDomainEvent directly instead of Axis.Shared.Domain.Primitives.IDomainEvent in the test record declaration.

Suggested fix
 using Axis.DataModeling.Infrastructure.Messaging;
+using Axis.Shared.Domain.Primitives;
 using FluentAssertions;
@@
-    private sealed record UnknownTestDomainEvent : Axis.Shared.Domain.Primitives.IDomainEvent;
+    private sealed record UnknownTestDomainEvent : IDomainEvent;

As per coding guidelines, "Use using statements instead of fully qualified class names (FQCN)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/Modules/DataModeling/Axis.DataModeling.Infrastructure.Tests/Messaging/DataModelingEventMapperTests.cs`
at line 51, The test declares the record UnknownTestDomainEvent implementing the
fully-qualified interface Axis.Shared.Domain.Primitives.IDomainEvent; replace
the FQCN with a using directive and implement IDomainEvent directly. Add a
`using Axis.Shared.Domain.Primitives;` at the top of the test file (or ensure
the namespace is already imported) and change the record declaration to `private
sealed record UnknownTestDomainEvent : IDomainEvent;` so the code uses the
simple interface name instead of the fully-qualified name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Modules/DataModeling/Axis.DataModeling.Domain/Aggregates/DataModel.cs`:
- Around line 88-89: The UpdateField and RemoveField code paths in the DataModel
aggregate must raise domain events like AddField does: after mutating the field
in DataModel.UpdateField call RaiseDomainEvent(new FieldUpdated(...)) with the
same payload shape used by FieldAdded (Id, OrganizationId, field.Id, field.Name,
field.Type, field.Label, field.IsRequired, field.DisplayOrder) and after
removing a field in DataModel.RemoveField call RaiseDomainEvent(new
FieldRemoved(...)) with those same identifying field properties; place the
RaiseDomainEvent calls immediately after the state change so downstream
consumers receive the lifecycle events.

---

Outside diff comments:
In `@scripts/register-avro-schemas.sh`:
- Line 8: Define and export the WB_SCHEMA_DIR and DM_SCHEMA_DIR variables before
any use to avoid unbound variable errors under set -u; initialize them (e.g.,
WB_SCHEMA_DIR="$ROOT/src/Modules/WorkflowBuilder/Axis.WorkflowBuilder.Contracts/Schemas"
and DM_SCHEMA_DIR="$ROOT/…/DesiredModule/…/Schemas") near the top of the script,
then use those variables in the register calls instead of relying on SCHEMA_DIR
being present, ensuring any register "$WB_SCHEMA_DIR/..." and register
"$DM_SCHEMA_DIR/..." invocations reference existing variables.

---

Nitpick comments:
In
`@tests/Modules/DataModeling/Axis.DataModeling.Infrastructure.Tests/Messaging/DataModelingEventMapperTests.cs`:
- Line 51: The test declares the record UnknownTestDomainEvent implementing the
fully-qualified interface Axis.Shared.Domain.Primitives.IDomainEvent; replace
the FQCN with a using directive and implement IDomainEvent directly. Add a
`using Axis.Shared.Domain.Primitives;` at the top of the test file (or ensure
the namespace is already imported) and change the record declaration to `private
sealed record UnknownTestDomainEvent : IDomainEvent;` so the code uses the
simple interface name instead of the fully-qualified name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c895cd5-8214-479f-abce-521f12c46930

📥 Commits

Reviewing files that changed from the base of the PR and between ed76979 and 2a3f275.

⛔ Files ignored due to path filters (9)
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/DataClassCreatedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/DataClassDeletedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/DataRecordCreatedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/DataRecordDeletedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/FieldAddedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/FieldRemovedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/FieldUpdatedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/ModelCreatedEvent.cs is excluded by !**/generated/**
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Generated/axis/datamodeling/events/ModelDeletedEvent.cs is excluded by !**/generated/**
📒 Files selected for processing (29)
  • Axis.sln
  • docs/PROGRESS.md
  • docs/epics/E03-data-modeling/README.md
  • docs/epics/E03-data-modeling/features/F01-model-definition.md
  • docs/epics/README.md
  • scripts/register-avro-schemas.sh
  • src/Axis.Api/Program.cs
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Axis.DataModeling.Contracts.csproj
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/DataModelingEventExtensions.cs
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/DataModelingKafkaTopics.cs
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/DataClassCreatedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/DataClassDeletedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/DataRecordCreatedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/DataRecordDeletedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/FieldAddedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/FieldRemovedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/FieldUpdatedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/ModelCreatedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Contracts/Schemas/ModelDeletedEvent.avsc
  • src/Modules/DataModeling/Axis.DataModeling.Domain/Aggregates/DataModel.cs
  • src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldAdded.cs
  • src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldRemoved.cs
  • src/Modules/DataModeling/Axis.DataModeling.Domain/Events/FieldUpdated.cs
  • src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Axis.DataModeling.Infrastructure.csproj
  • src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Messaging/DataModelingEventMapper.cs
  • src/Modules/DataModeling/Axis.DataModeling.Infrastructure/Persistence/DataModelingUnitOfWork.cs
  • tests/Architecture/Axis.Architecture.Tests/Axis.Architecture.Tests.csproj
  • tests/Modules/DataModeling/Axis.DataModeling.Domain.Tests/DataModelTests.cs
  • tests/Modules/DataModeling/Axis.DataModeling.Infrastructure.Tests/Messaging/DataModelingEventMapperTests.cs

…101 review)

Co-authored-by: Phuong Nguyen <phuongnse@users.noreply.github.com>
@phuongnse phuongnse merged commit 59912bb into main May 24, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants