Skip to content

Latest commit

 

History

History

NTangle.Template

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

NTangle.Template

This is the .NET template used to accelerate the creation of an nTangle solution and all projects using dotnet new. This leverages the .NET Core templating functionality.

This article covers the following:

Note: the SQL Server Agent service must be running for CDC to function correctly.


Installation

Before the NTangle.Template template can be used it must be installed from NuGet. The dotnet new install command is used to perform this.

-- Use the latest published from NuGet...
dotnet new install ntangle.template --nuget-source https://api.nuget.org/v3/index.json

-- Or alternatively, point to a local folder...
dotnet new install ntangle.template --nuget-source C:\Users\Name\nuget-publish

To verify once installed, execute dotnet new list ntangle; this will output the following.

These templates matched your input: 'ntangle'

Template Name     Short Name  Language  Tags
----------------  ----------  --------  -------------------------------------
NTangle solution  ntangle     [C#]      NTangle/CDC/Database/Console/Solution

Create solution

To create the Solution you must first be in the directory that you intend to create the artefacts within. The directory name is then used as the default for the application name.

The dotnet new command is used to create the initial solution artefacts that will leverage Microsoft SQL Server. Additionally, a publisher option can be specified to indicate what type of Publisher project should be created; the values are Console (default), Function (Azure Functions), or None.

dotnet new ntangle
dotnet new ntangle -publisher Console
dotnet new ntangle -publisher Function

What is created?

The following solution and projects will be created within the root AppName folder.

└── AppName
  └── AppName.CodeGen     # Code generation console (tooling)
  └── AppName.Database    # Source database console (tooling)
  └── AppName.Publisher   # Runtime orchestration and event publishing console
  └── AppName.SidecarDb   # Sidecar database console (tooling)
  └── AppName.sln         # Solution file that references all above projects

The solution and projects created contain all the requisite .NET Classes and NuGet references to build an NTangle solution.

Note: the solution will not initially compile. There are references within the AppName.Publisher/Program.cs that do not exist; these need to be created/generated.

The templated structure represents the bare minimum needed to start. Generally, the following projects are maintained in the sequence as follows.


AppName.Database

This represents the source database project that will be used to maintain any additional database schema and data, depending on what additional capability is required. This is not a mandatory project, and can be removed if not required where leveraging alternate database management tools for the existing source database.

The source connection string defaulted within the Program.cs needs to be validated and adjusted to that required for development purposes.

Execute dotnet run -- --help to see the available command line arguments.

The console application is generated with a reference to DbEx (please review this repo to further understand the database migration deployment capabilities).


AppName.CodeGen

This represents the code generation project that will be used to generate the nTangle artefact from the source database.

The source connection string defaulted within the Program.cs needs to be validated and adjusted to that of the source database.

Execute dotnet run -- --help to see the available command line arguments.

The key artefact that is maintained is the ntangle.yaml which contains the CDC-related table configuration used to drive the code-generation. The contents are provided as an example only, and generally would be removed and replaced with the actual configuration.

To execute the code-generation use dotnet run, or execute the AppName.CodeGen.exe directly.


AppName.SidecarDb

This represents the sidecar database project that will be used to maintain the database migration scripts and schema objects that are required to support the Publisher project.

A console application is generated with a reference to DbEx (please review this repo to further understand the database migration deployment capabilities).

The sidecar connection string defaulted within the Program.cs needs to be validated and adjusted to that required for development purposes.

The required artefacts are generally added to this project using the earlier code-generator (see AppName.CodeGen).

To execute the database migration use dotnet run all, or execute the AppName.Database.exe all directly.


AppName.Publisher

This default publisher is a console application whose purpose is to orchestrate the CDC processing and corresponding event processing leveraging all of the generated artefacts, both in the sidecar database, and within the publisher itself.

The connection strings defaulted within the appsettings.json needs to be validated and adjusted to that required for development purposes.


Demonstration

The following walks through the process of demonstrating how to execute the end-to-end functionality of NTangle. For the purposes of the demo the application name will be FooBar.


Create database

An existing database is required that contains tables and data. To create the database and set up, copy the contents of create-database.sql and execute using your favorite database tool. This will also turn on CDC for the database after it is created.

The following tables, with the relationships described, will be created.

Contact             // Root (aggregate)
└── Address         // Child 0:n - Zero--to-many addresses (e.g. Home and/or Postal)
  └── AddressType   // Child 1:1 - One-to-one address type (reference data)

Create solution

To start, create a new FooBar directory, change to that directory, and then create the solution using the NTangle template. For the purposes of this demo the default console-based publisher will be used. Once created, open the solution in Visual Studio.

mkdir FooBar
cd FooBar
dotnet new ntangle

FooBar.CodeGen

The ntangle.yaml configuration file is largely pre-configured. For the purposes of this demo, the root property cdcEnable should be set to true; this will enable the generation of the TSQL to enable CDC on each of the tables.

The Program.cs has a default connection string, this needs to be validated and adjusted to that required to enable access to the required SQL Server instance.

Compile the application and execute directly from Visual Studio, or using dotnet run. The output from the console application should be similar to the following.

╔╗╔╔╦╗┌─┐┌┐┌┌─┐┬  ┌─┐  ╔═╗┌─┐┌┬┐┌─┐  ╔═╗┌─┐┌┐┌  ╔╦╗┌─┐┌─┐┬
║║║ ║ ├─┤││││ ┬│  ├┤   ║  │ │ ││├┤───║ ╦├┤ │││   ║ │ ││ ││
╝╚╝ ╩ ┴ ┴┘└┘└─┘┴─┘└─┘  ╚═╝└─┘─┴┘└─┘  ╚═╝└─┘┘└┘   ╩ └─┘└─┘┴─┘

FooBar.CodeGen Code Generation Tool.

Config = ntangle.yaml
Script = SqlServerDbExSidecar.yaml
OutDir = C:\ntangle-demo\FooBar
ExpectNoChanges = False
IsSimulation = False
Parameters:
  CreateDatabase = System.Func`2[System.String,CoreEx.Database.IDatabase]
  AppName = FooBar
  UseSidecar = True
Assemblies:
  FooBar.CodeGen, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
  NTangle.CodeGen, Version=3.0.0.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  DbEx.SqlServer, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  DbEx, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  Querying database to infer table(s)/column(s) schema...
    Database schema query complete [1147.6465ms]


Scripts:
 Template: SpExecuteBatch_sidecar_sql.hbs (TableCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\NTangle\Stored Procedures\Generated\spContactBatchExecute.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SpTrackingBatch_sql.hbs (TableCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\NTangle\Stored Procedures\Generated\spContactBatchTracking.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: ExecuteBatch_sql.hbs (TableCodeGenerator: Publisher/Resources)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Resources\Generated\ContactExecuteBatch.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: ExecuteExplicit_sql.hbs (TableCodeGenerator: Publisher/Resources)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Resources\Generated\ContactExecuteExplicit.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SpCompleteBatch_sql.hbs (TableCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\NTangle\Stored Procedures\Generated\spContactBatchComplete.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SpResetBatch_sql.hbs (TableCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\NTangle\Stored Procedures\Generated\spContactBatchReset.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SpIdentifierMappingCreate_sql.hbs (IdentifierMappingCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
  [Files: Unchanged = 0, Updated = 0, Created = 0]
 Template: SpEventOutboxEnqueue_sql.hbs (OutboxCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\Outbox\Stored Procedures\Generated\spEventOutboxEnqueue.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SpEventOutboxDequeue_sql.hbs (OutboxCodeGenerator: SidecarDb/Schema/Xxx/Stored Procedures)
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\Outbox\Stored Procedures\Generated\spEventOutboxDequeue.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: Entity_cs.hbs (TableCodeGenerator: Publisher/Entities)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Entities\Generated\ContactCdc.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: EntityOrchestrator_cs.hbs (TableCodeGenerator: Publisher/Data)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Data\Generated\ContactOrchestrator.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: EventOutboxEnqueue_cs.hbs (OutboxCodeGenerator: Publisher/Events)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Data\Generated\EventOutboxEnqueue.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: EventOutboxDequeue_cs.hbs (OutboxCodeGenerator: Publisher/Events)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Data\Generated\EventOutboxDequeue.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: IServiceCollectionExtensions_cs.hbs (RootCodeGenerator: Publisher)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Generated\IServiceCollectionExtensions.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: EntityHostedService_cs.hbs (RootCodeGenerator: Publisher/Services)
    Created -> C:\ntangle-demo\FooBar\FooBar.Publisher\Services\Generated\ContactHostedService.cs
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: EntityService_cs.hbs (RootCodeGenerator: Publisher/Services)
  [Files: Unchanged = 0, Updated = 0, Created = 0]
 Template: SchemaCdc_sql.hbs (CdcSchemaCreateCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000032-01-create-ntangle-schema.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: TableVersionTracking_sql.hbs (RootCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000033-02-create-ntangle-versiontracking-table.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: TableBatchTracking_sql.hbs (TableCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000033-03-create-ntangle-contactbatchtracking-table.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: SchemaEventOutbox_sql.hbs (OutboxCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000033-04-create-outbox-eventoutbox-schema.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: TableEventOutbox_sql.hbs (OutboxCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000033-05-create-outbox-eventoutbox-table.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: TableEventOutboxData_sql.hbs (OutboxCodeGenerator: SidecarDb/Migrations (GenOnce))
    Created -> C:\ntangle-demo\FooBar\FooBar.SidecarDb\Migrations\20241112-000033-06-create-outbox-eventoutboxdata-table.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]
 Template: TableIdentifierMapping_sql.hbs (IdentifierMappingCodeGenerator: SidecarDb/Migrations (GenOnce))
  [Files: Unchanged = 0, Updated = 0, Created = 0]
 Template: CdcEnable_sql.hbs (CdcEnableCodeGenerator: Database/Migrations)
    Created -> C:\ntangle-demo\FooBar\FooBar.Database\Migrations\CdcEnable.post.deploy.sql
  [Files: Unchanged = 0, Updated = 0, Created = 1]

FooBar.CodeGen Complete. [1467ms, Files: Unchanged = 0, Updated = 0, Created = 21, TotalLines = 1221]

FooBar.Database

As DbEx is being used, the generated artefacts are included in the project automatically. The source artefacts are as follows.

└── Schema
  └── Migrations
    └── CdcEnable.post.deploy.sql

The Program.cs has a default connection string, this needs to be validated and adjusted to that required to enable access to the required SQL Server instance. Compile the application and execute directly from Visual Studio (after setting the Application arguments to all within the Debug tab of the Project Properties), or using dotnet run migrate.

The output from the console application should be similar to the following.

╔╦╗┌┐ ╔═╗─┐ ┬  ╔╦╗┌─┐┌┬┐┌─┐┌┐ ┌─┐┌─┐┌─┐  ╔╦╗┌─┐┌─┐┬
 ║║├┴┐║╣ ┌┴┬┘   ║║├─┤ │ ├─┤├┴┐├─┤└─┐├┤    ║ │ ││ ││
═╩╝└─┘╚═╝┴ └─  ═╩╝┴ ┴ ┴ ┴ ┴└─┘┴ ┴└─┘└─┘   ╩ └─┘└─┘┴─┘

FooBar.Database Database Tool. [SQL Server]

Command = Migrate
Provider = SqlServer
SchemaOrder = dbo
OutDir = C:\ntangle-demo\FooBar\FooBar.Database
Parameters:
  DatabaseName = FooBar
  JournalSchema = dbo
  JournalTable = SchemaVersions
Assemblies:
  DbEx, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  DbEx.SqlServer, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  FooBar.Database, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

--------------------------------------------------------------------------------

DATABASE MIGRATE: Migrating the database...
  Probing for embedded resources: FooBar.Database.Migrations.*.sql, DbEx.SqlServer.Migrations.*.sql, DbEx.Migrations.*.sql
  Execute the embedded resources...
    FooBar.Database.Migrations.CdcEnable.post.deploy.sql (RES)

Complete. [359.7084ms]

--------------------------------------------------------------------------------

FooBar.Database Complete. [371.9879ms]

FooBar.SidecarDb

As DbEx is being used, the generated artefacts are included in the project automatically. The sidecar artefacts are as follows.

└── Migrations
  └── yyyymmdd-hhmmss-01-create-ntangle-schema.sql
  └── yyyymmdd-hhmmss-02-create-ntangle-versiontracking-table.sql
  └── yyyymmdd-hhmmss-03-create-ntangle-contactbatchtracking-table.sql
  └── yyyymmdd-hhmmss-04-create-outbox-eventoutbox-schema.sql
  └── yyyymmdd-hhmmss-05-create-outbox-eventoutbox-table.sql
  └── yyyymmdd-hhmmss-06-create-outbox-eventoutboxdata-table.sql
└── Schema
  └── NTangle
    └── Stored Procedures
      └── Generated
        └── spContactBatchComplete.sql
        └── spContactBatchExecute.sql
        └── spContactBatchReset.sql
        └── spContactBatchTracking.sql
  └── Outbox
    └── Stored Procedures
      └── Generated
        └── spEventOutboxDequeue.sql
        └── spEventOutboxEnqueue.sql

The Program.cs has a default connection string, this needs to be validated and adjusted to that required to enable access to the required SQL Server instance. Compile the application and execute directly from Visual Studio (after setting the Application arguments to all within the Debug tab of the Project Properties), or using dotnet run all.

The output from the console application should be similar to the following.

╔╦╗┌┐ ╔═╗─┐ ┬  ╔╦╗┌─┐┌┬┐┌─┐┌┐ ┌─┐┌─┐┌─┐  ╔╦╗┌─┐┌─┐┬
 ║║├┴┐║╣ ┌┴┬┘   ║║├─┤ │ ├─┤├┴┐├─┤└─┐├┤    ║ │ ││ ││
═╩╝└─┘╚═╝┴ └─  ═╩╝┴ ┴ ┴ ┴ ┴└─┘┴ ┴└─┘└─┘   ╩ └─┘└─┘┴─┘

FooBar.SidecarDb Database Tool. [SQL Server]

Command = All
Provider = SqlServer
SchemaOrder = dbo
OutDir = C:\ntangle-demo\FooBar\FooBar.SidecarDb
Parameters:
  DatabaseName = FooBar_Sidecar
  JournalSchema = dbo
  JournalTable = SchemaVersions
Assemblies:
  DbEx, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  DbEx.SqlServer, Version=2.7.1.0, Culture=neutral, PublicKeyToken=10b60143e92943c1
  FooBar.SidecarDb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

--------------------------------------------------------------------------------

DATABASE CREATE: Checking database existence and creating where not found...
  Create database...
    Database 'FooBar_Sidecar' did not exist and was created.

  Probing for 'post.database.create' embedded resources: FooBar.SidecarDb.Migrations.*.sql, DbEx.SqlServer.Migrations.*.sql, DbEx.Migrations.*.sql
  ** Nothing found. **

Complete. [576.864ms]

--------------------------------------------------------------------------------

DATABASE MIGRATE: Migrating the database...
  Probing for embedded resources: FooBar.SidecarDb.Migrations.*.sql, DbEx.SqlServer.Migrations.*.sql, DbEx.Migrations.*.sql
  Execute the embedded resources...
    *Journal table did not exist within the database and was automatically created.
    FooBar.SidecarDb.Migrations.20241112-000032-01-create-ntangle-schema.sql (RES)
    FooBar.SidecarDb.Migrations.20241112-000033-02-create-ntangle-versiontracking-table.sql (RES)
    FooBar.SidecarDb.Migrations.20241112-000033-03-create-ntangle-contactbatchtracking-table.sql (RES)
    FooBar.SidecarDb.Migrations.20241112-000033-04-create-outbox-eventoutbox-schema.sql (RES)
    FooBar.SidecarDb.Migrations.20241112-000033-05-create-outbox-eventoutbox-table.sql (RES)
    FooBar.SidecarDb.Migrations.20241112-000033-06-create-outbox-eventoutboxdata-table.sql (RES)

Complete. [259.1122ms]

--------------------------------------------------------------------------------

DATABASE SCHEMA: Drops and creates the database objects...
  Probing for files (recursively): C:\ntangle-demo\FooBar\FooBar.SidecarDb\Schema\*\*.sql
  Probing for embedded resources: FooBar.SidecarDb.Schema.*.sql, DbEx.SqlServer.Schema.*.sql, DbEx.Schema.*.sql

  Drop known schema objects...
    ** Note: All schema objects implement replace functionality and therefore there is no need to drop existing. **

  Create (or replace) known schema objects...
    FooBar.SidecarDb.Schema.NTangle.Stored_Procedures.Generated.spContactBatchComplete.sql (FILE) > CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchComplete]
    FooBar.SidecarDb.Schema.NTangle.Stored_Procedures.Generated.spContactBatchExecute.sql (FILE) > CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchExecute]
    FooBar.SidecarDb.Schema.NTangle.Stored_Procedures.Generated.spContactBatchReset.sql (FILE) > CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchReset]
    FooBar.SidecarDb.Schema.NTangle.Stored_Procedures.Generated.spContactBatchTracking.sql (FILE) > CREATE OR ALTER PROCEDURE [NTangle].[spContactBatchTracking]
    FooBar.SidecarDb.Schema.Outbox.Stored_Procedures.Generated.spEventOutboxDequeue.sql (FILE) > CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxDequeue]
    FooBar.SidecarDb.Schema.Outbox.Stored_Procedures.Generated.spEventOutboxEnqueue.sql (FILE) > CREATE OR ALTER PROCEDURE [Outbox].[spEventOutboxEnqueue]

Complete. [86.1015ms]

--------------------------------------------------------------------------------

DATABASE DATA: Insert or merge the embedded data [yaml|json|sql]...
  Probing for embedded resources: DbEx.Data.*, DbEx.SqlServer.Data.*, FooBar.SidecarDb.Data.*
  ** Nothing found. **

Complete. [8.7158ms]

--------------------------------------------------------------------------------

FooBar.SidecarDb Complete. [951.4284ms]

FooBar.Publisher

The Program.cs is pre-configured, and the appsettings.json has the default connection strings, these need to be validated and adjusted to that required to enable access to the required SQL Server instance. All of the generated C# artefacts should have been automatically included within the .NET project. Compile the application and execute. Leave it running; use ctrl-c to stop once the following test has been performed.

For the purposes of demonstration the EventOutboxHostedService has been configured to use the LoggerEventSender; this would need to be changed to use an appropriate IEventSender to send the events to an actual messaging system, e.g. Azure ServiceBusSender.

Within your favorite database tool make a change to [Legacy].[Contact] table, updating the Name column in the first row. Within the next 5-10 seconds a similar console output to the following should be displayed.

info: FooBar.Publisher.Data.ContactOrchestrator[0]
      ContactOrchestrator: Batch '1': 1 entity operations(s) were found. [MaxQuerySize=100, ContinueWithDataLoss=True, CorrelationId=4e5bd316-6c8e-466f-8cd1-90dc1298439f, ExecutionId=534c33af-d522-4d70-af2b-680ef2ad11a4, Elapsed=65.3063ms]
info: FooBar.Publisher.Data.ContactOrchestrator[0]
      ContactOrchestrator: Batch '1': 1 event(s) were published successfully. [Publisher=EventPublisher, CorrelationId=4e5bd316-6c8e-466f-8cd1-90dc1298439f, ExecutionId=534c33af-d522-4d70-af2b-680ef2ad11a4, Elapsed=312.5908ms]
info: FooBar.Publisher.Data.ContactOrchestrator[0]
      ContactOrchestrator: Batch '1': Marked as Completed. [CorrelationId=4e5bd316-6c8e-466f-8cd1-90dc1298439f, ExecutionId=534c33af-d522-4d70-af2b-680ef2ad11a4, Elapsed=19.1083ms]
info: CoreEx.Events.LoggerEventSender[0]
      Event[0].Metadata = {
        "id": "391e96d5-85eb-4e24-8c65-cf8c0a9d4143",
        "subject": "legacy.contact",
        "action": "updated",
        "type": "legacy.contact",
        "source": "/database/cdc/legacy/contact/1",
        "timestamp": "2024-11-12T00:12:09.8691761+00:00",
        "correlationId": "4e5bd316-6c8e-466f-8cd1-90dc1298439f",
        "key": "1",
        "etag": "T8jUuxwOIxqXENSPAHEGwnPsD9kwrtyzdGI/acjRrcI="
      }
      Event[0].Data = {
        "specversion": "1.0",
        "id": "391e96d5-85eb-4e24-8c65-cf8c0a9d4143",
        "time": "2024-11-12T00:12:09.8691761Z",
        "type": "legacy.contact",
        "source": "/database/cdc/legacy/contact/1",
        "subject": "legacy.contact",
        "action": "updated",
        "correlationid": "4e5bd316-6c8e-466f-8cd1-90dc1298439f",
        "etag": "T8jUuxwOIxqXENSPAHEGwnPsD9kwrtyzdGI/acjRrcI=",
        "key": "1",
        "datacontenttype": "application/json",
        "data": {
          "id": 1,
          "name": "Bob",
          "phone": "123",
          "addresses": [
            {
              "id": 11,
              "street1": "1st",
              "street2": "Seattle",
              "type": "H"
            },
            {
              "id": 12,
              "street1": "Main",
              "street2": "Redmond",
              "type": "P"
            }
          ],
          "etag": "T8jUuxwOIxqXENSPAHEGwnPsD9kwrtyzdGI/acjRrcI="
        }
      }