You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Goodtocode.Domain provides foundational types for building DDD, clean architecture, and event-driven systems. It includes base classes for domain entities, audit fields, domain events, and secured/multi-tenant entities. The library is lightweight, dependency-free, and designed to work with EF Core, Cosmos DB, Table Storage, or custom repositories.
7
+
Goodtocode.Domain provides foundational types for building DDD, clean architecture, and event-driven systems. It includes base classes for domain entities, audit fields, domain events, secured/multi-tenant entities, and immutable versioned entities with full lifecycle management. The library is lightweight, dependency-free, and designed to work with EF Core, Cosmos DB, Table Storage, or custom repositories.
8
8
9
9
## Target Frameworks
10
10
- Library: `netstandard2.1`
@@ -14,10 +14,14 @@ Goodtocode.Domain provides foundational types for building DDD, clean architectu
14
14
- Domain entity base with audit fields (`CreatedOn`, `ModifiedOn`, `DeletedOn`, `Timestamp`)
15
15
- Domain event pattern and dispatcher (`IDomainEvent`, `IDomainHandler`, `DomainDispatcher`)
16
16
- Equality and identity management for aggregate roots
17
-
- Partition key and row key support for document/table stores (`PartitionKey` and `RowKey` both default to `Id.ToString()` unless specified)
17
+
- UUIDv7-based `RowKey` for time-ordered, chronologically sortable storage keys
18
+
- Partition key and row key support for document/table stores (`PartitionKey` defaults to `Id.ToString()`; `RowKey` is always a new UUIDv7 — distinct from `Id`)
18
19
- Secured entity base for multi-tenancy and ownership (`OwnerId`, `TenantId`, `CreatedBy`, `ModifiedBy`, `DeletedBy`)
19
20
- Extension methods for authorization and ownership queries
20
21
-**Invariant state protection** for audit and security fields (fields are only set if not already set, ensuring consistency and preventing accidental overwrites)
22
+
-**Immutable versioned entities** via `SecuredVersionedEntity<TModel>`: all state changes produce new rows; no in-place mutation after persistence
23
+
- Full versioning lifecycle: `CreateNextVersion()`, `CreateSuccessor()`, `Freeze()`, `MarkNotLatest()`
24
+
- Abstract factory pattern (`CreateNextVersionCore` / `CreateSuccessorCore`) keeps derived classes in control of construction
-`DomainEntity<TModel>`: Base entity with audit fields (`CreatedOn`, `ModifiedOn`, `DeletedOn`, `Timestamp`), identity (`Id`), partition key, row key, and domain event tracking.
45
-
-`PartitionKey` and `RowKey` both default to `Id.ToString()` unless explicitly set. This supports portability across Cosmos DB, Table Storage, and other stores. If you do not specify a value, both will be the same by default.
49
+
-`RowKey` is always a new **UUIDv7** (time-ordered, RFC 4122). It is intentionally distinct from `Id`. `PartitionKey` defaults to `Id.ToString()` unless explicitly set. This supports portability across Cosmos DB, Table Storage, and other stores.
46
50
-`SecuredEntity<TModel>`: Extends `DomainEntity<TModel>` with `OwnerId`, `TenantId`, and audit fields for user actions (`CreatedBy`, `ModifiedBy`, `DeletedBy`). `PartitionKey` defaults to `TenantId.ToString()` for multi-tenant isolation.
47
51
-**Invariant state protection**: Methods like `MarkCreated`, `MarkDeleted`, etc. only set fields if not already set, ensuring entity state is consistent and protected from accidental changes.
52
+
-`SecuredVersionedEntity<TModel>`: Extends `SecuredEntity<TModel>` and implements `IVersionable`. All persisted rows are **immutable** — state changes always produce new rows. `PartitionKey` is `TenantId:CanonicalKey`, grouping all versions of a logical entity in the same partition.
53
+
-`IVersionable`: Read-only state contract — `CanonicalKey`, `Version`, `PreviousVersionId`, `IsLatest`, `IsPinned`, `IsFrozen`. No mutation methods on the interface.
48
54
- Domain events: Implement `IDomainEvent<TModel>` and dispatch with `DomainDispatcher`.
49
55
56
+
## Versioning Lifecycle & Invariants
57
+
58
+
`SecuredVersionedEntity<TModel>` enforces the following invariants:
59
+
60
+
| Rule | Detail |
61
+
|------|--------|
62
+
| Rows are immutable | Once persisted, a row's fields never change (except `IsLatest` and `IsFrozen` via `MarkNotLatest`/`Freeze`) |
63
+
| New version = new row |`CreateNextVersion()` returns a new row with a new `Id`, new UUIDv7 `RowKey`, incremented `Version`, and `PreviousVersionId` pointing to the current row |
64
+
|`IsLatest` is caller-managed | The caller must call `MarkNotLatest()` on the previous row and persist both rows **transactionally**|
65
+
| Frozen series cannot version |`CreateNextVersion()` throws `InvalidOperationException` when `IsFrozen = true`|
66
+
| Successors start fresh |`CreateSuccessor(newCanonicalKey)` starts a new series: `Version = 1`, `PreviousVersionId = null`, same `TenantId`/`OwnerId`|
67
+
| Successors are always allowed |`CreateSuccessor()` is permitted even on a frozen series |
68
+
| Derived classes own construction |`CreateNextVersionCore()` and `CreateSuccessorCore()` are `abstract` — the concrete class supplies the new instance |
`SecuredVersionedEntity<TModel>` uses the **Template Method** pattern. Your derived class provides `CreateNextVersionCore()` and `CreateSuccessorCore()`; the base class enforces all invariants.
0 commit comments