Backend (.NET 10): - EventFlow CQRS/Event Sourcing with PostgreSQL - GraphQL.NET API with mutations and queries - Custom ReadModelSqlGenerator for snake_case PostgreSQL columns - Hangfire for background job processing - Integration tests with isolated test databases Frontend (React/Vite): - Initial project structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
80 lines
2.8 KiB
C#
80 lines
2.8 KiB
C#
using System.ComponentModel.DataAnnotations.Schema;
|
|
using Books.Api.Domain.Companies;
|
|
using Books.Api.Domain.Companies.Events;
|
|
using EventFlow.Aggregates;
|
|
using EventFlow.PostgreSql.ReadStores.Attributes;
|
|
using EventFlow.ReadStores;
|
|
|
|
namespace Books.Api.EventFlow.ReadModels;
|
|
|
|
[Table("company_read_models")]
|
|
public class CompanyReadModel : IReadModel,
|
|
IAmReadModelFor<CompanyAggregate, CompanyId, CompanyCreatedEvent>,
|
|
IAmReadModelFor<CompanyAggregate, CompanyId, CompanyUpdatedEvent>
|
|
{
|
|
// EventFlow standard columns
|
|
[PostgreSqlReadModelIdentityColumn]
|
|
public string AggregateId { get; set; } = string.Empty;
|
|
|
|
public DateTimeOffset CreateTime { get; set; }
|
|
public DateTimeOffset UpdatedTime { get; set; }
|
|
|
|
[PostgreSqlReadModelVersionColumn]
|
|
public int LastAggregateSequenceNumber { get; set; }
|
|
|
|
// Business columns
|
|
public string Name { get; set; } = string.Empty;
|
|
public string? Cvr { get; set; }
|
|
public string? Address { get; set; }
|
|
public string? PostalCode { get; set; }
|
|
public string? City { get; set; }
|
|
public string Country { get; set; } = "DK";
|
|
public int FiscalYearStartMonth { get; set; } = 1;
|
|
public string Currency { get; set; } = "DKK";
|
|
public bool VatRegistered { get; set; }
|
|
public string? VatPeriodFrequency { get; set; }
|
|
|
|
public Task ApplyAsync(
|
|
IReadModelContext context,
|
|
IDomainEvent<CompanyAggregate, CompanyId, CompanyCreatedEvent> domainEvent,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var e = domainEvent.AggregateEvent;
|
|
AggregateId = domainEvent.AggregateIdentity.Value;
|
|
CreateTime = domainEvent.Timestamp;
|
|
UpdatedTime = domainEvent.Timestamp;
|
|
LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
|
|
Name = e.Name;
|
|
Cvr = e.Cvr;
|
|
Address = e.Address;
|
|
PostalCode = e.PostalCode;
|
|
City = e.City;
|
|
Country = e.Country;
|
|
FiscalYearStartMonth = e.FiscalYearStartMonth;
|
|
Currency = e.Currency;
|
|
VatRegistered = e.VatRegistered;
|
|
VatPeriodFrequency = e.VatPeriodFrequency;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task ApplyAsync(
|
|
IReadModelContext context,
|
|
IDomainEvent<CompanyAggregate, CompanyId, CompanyUpdatedEvent> domainEvent,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var e = domainEvent.AggregateEvent;
|
|
UpdatedTime = domainEvent.Timestamp;
|
|
LastAggregateSequenceNumber = (int)domainEvent.AggregateSequenceNumber;
|
|
Name = e.Name;
|
|
Cvr = e.Cvr;
|
|
Address = e.Address;
|
|
PostalCode = e.PostalCode;
|
|
City = e.City;
|
|
Country = e.Country;
|
|
FiscalYearStartMonth = e.FiscalYearStartMonth;
|
|
Currency = e.Currency;
|
|
VatRegistered = e.VatRegistered;
|
|
VatPeriodFrequency = e.VatPeriodFrequency;
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|