{"id":13629011,"url":"https://github.com/TheArchitectDev/Architect.DomainModeling","last_synced_at":"2025-04-17T04:32:49.087Z","repository":{"id":38452661,"uuid":"480424961","full_name":"TheArchitectDev/Architect.DomainModeling","owner":"TheArchitectDev","description":"A complete Domain-Driven Design (DDD) toolset for implementing domain models, including base types and source generators.","archived":false,"fork":false,"pushed_at":"2025-01-12T14:20:28.000Z","size":215,"stargazers_count":64,"open_issues_count":0,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-12T15:29:39.452Z","etag":null,"topics":["csharp-sourcegenerator","ddd","domain","domain-driven-design","domain-modeling","entities","entity","generator","modeling","source","source-generator","value-object","value-objects"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/Architect.DomainModeling/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheArchitectDev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-04-11T14:50:39.000Z","updated_at":"2025-01-12T14:20:26.000Z","dependencies_parsed_at":"2024-01-06T02:09:58.013Z","dependency_job_id":null,"html_url":"https://github.com/TheArchitectDev/Architect.DomainModeling","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheArchitectDev%2FArchitect.DomainModeling","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheArchitectDev%2FArchitect.DomainModeling/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheArchitectDev%2FArchitect.DomainModeling/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheArchitectDev%2FArchitect.DomainModeling/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheArchitectDev","download_url":"https://codeload.github.com/TheArchitectDev/Architect.DomainModeling/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249316023,"owners_count":21249878,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp-sourcegenerator","ddd","domain","domain-driven-design","domain-modeling","entities","entity","generator","modeling","source","source-generator","value-object","value-objects"],"created_at":"2024-08-01T22:01:01.527Z","updated_at":"2025-04-17T04:32:45.249Z","avatar_url":"https://github.com/TheArchitectDev.png","language":"C#","readme":"# Architect.DomainModeling\r\n\r\nA complete Domain-Driven Design (DDD) toolset for implementing domain models, including base types and source generators.\r\n\r\n- Base types, including: `ValueObject`, `WrapperValueObject`, `Entity`, `IIdentity`, `IApplicationService`, `IDomainService`.\r\n- Source generators, for types including: `ValueObject`, `WrapperValueObject`, `DummyBuilder`, `IIdentity`.\r\n- Structural implementations for hash codes and equality on collections (also used automatically by source-generated value objects containing collections).\r\n- (De)serialization support, such as for JSON.\r\n- Optional generated mapping code for Entity Framework.\r\n\r\n## Source Generators\r\n\r\nThis package uses source generators (introduced in .NET 5). Source generators write additional C# code as part of the compilation process.\r\n\r\nAmong other advantages, source generators enable IntelliSense on generated code. They are primarily used here to generate boilerplate code, such as overrides of `ToString()`, `GetHashCode()`, and `Equals()`, as well as operator overloads.\r\n\r\n## Domain Object Types\r\n\r\n### ValueObject\r\n\r\nA value object is an an immutable data model representing one or more values. Such an object is identified and compared by its values. Value objects cannot be mutated, but new ones with different values can be created. Built-in examples from .NET itself are `string` and `DateTime`.\r\n\r\nConsider the following type:\r\n\r\n```cs\r\npublic class Color : ValueObject\r\n{\r\n\tpublic ushort Red { get; private init; }\r\n\tpublic ushort Green { get; private init; }\r\n\tpublic ushort Blue { get; private init; }\r\n\r\n\tpublic Color(ushort red, ushort green, ushort blue)\r\n\t{\r\n\t\tthis.Red = red;\r\n\t\tthis.Green = green;\r\n\t\tthis.Blue = blue;\r\n\t}\r\n}\r\n```\r\n\r\nThis is the non-boilerplate portion of the value object, i.e. everything that we would like to define by hand. However, the type is missing the following:\r\n\r\n- A `ToString()` override.\r\n- A `GetHashCode()` override.\r\n- An `Equals()` override.\r\n- The `IEquatable\u003cColor\u003e` interface implementation.\r\n- Operator overloads for `==` and `!=` based on `Equals()`, since a value object only ever cares about its contents, never its reference identity.\r\n- Potentially the `IComparable\u003cColor\u003e` interface implementation.\r\n- Correctly configured nullable reference types (`?` vs. no `?`) on all mentioned boilerplate code.\r\n- Unit tests on any _hand-written_ boilerplate code.\r\n\r\nChange the type as follows to have source generators tackle all of the above and more:\r\n\r\n```cs\r\n[ValueObject]\r\npublic partial class Color\r\n{\r\n\t// Snip\r\n}\r\n```\r\n\r\nNote that the `ValueObject` base class is now optional, as the generated partial class implements it.\r\n\r\nThe `IComparable\u003cColor\u003e` interface can optionally be added, if the type is considered to have a natural order. In such case, the type's properties are compared in the order in which they are defined. When adding the interface, make sure that the properties are defined in the intended order for comparison.\r\n\r\nAlterantively, if we inherit from `ValueObject` but omit the `[ValueObject]` attribute, we get partial benefits:\r\n\r\n- Overriding `ToString()` is made mandatory before the type will build.\r\n- `GetHashCode()` and `Equals()` are overridden to throw a `NotSupportedException`. Value objects should use structural equality, and an exception is better than unintentional reference equality (i.e. bugs).\r\n- Operators `==` and `!=` are implemented to delegate to `Equals()`, to avoid unintentional reference equality.\r\n\r\n### WrapperValueObject\r\n\r\nA wrapper value object is a value object that represents and wraps a single value. For example, a domain model may define a `Description` value object, a string with certain restrictions on its length and permitted characters.\r\n\r\nThe wrapper value object is just another value object. Its existence is merely a technical detail to make it easier to implement value objects that represent a single value.\r\n\r\nConsider the following type:\r\n\r\n```cs\r\npublic class Description : WrapperValueObject\u003cstring\u003e\r\n{\r\n\tprotected override StringComparison StringComparison =\u003e StringComparison.Ordinal;\r\n\r\n\tpublic string Value { get; private init; }\r\n\r\n\tpublic Description(string value)\r\n\t{\r\n\t\tthis.Value = value ?? throw new ArgumentNullException(nameof(value));\r\n\r\n\t\tif (this.Value.Length == 0) throw new ArgumentException($\"A {nameof(Description)} must not be empty.\");\r\n\t\tif (this.Value.Length \u003e MaxLength) throw new ArgumentException($\"A {nameof(Description)} must not be over {MaxLength} characters long.\");\r\n\t\tif (ContainsNonPrintableCharacters(this.Value, flagNewLinesAndTabs: false)) throw new ArgumentException($\"A {nameof(Description)} must contain only printable characters.\");\r\n\t}\r\n}\r\n```\r\n\r\nBesides all the things that the value object in the previous section was missing, this type is missing the following:\r\n\r\n- An implementation of the `ContainsNonPrintableCharacters()` method.\r\n- An explicit conversion from `string` (explicit since not every string is a `Description`).\r\n- An implicit conversion to `string` (implicit since every `Description` is a valid `string`).\r\n- If the underlying type had been a value type (e.g. `int`), conversions from and to its nullable counterpart (e.g. `int?`).\r\n- Ideally, JSON converters that convert instances to and from `\"MyDescription\"` rather than `{\"Value\":\"MyDescription\"}`.\r\n\r\nChange the type as follows to have source generators tackle all of the above and more:\r\n\r\n```cs\r\n[WrapperValueObject\u003cstring\u003e]\r\npublic partial class Description\r\n{\r\n\t// Snip\r\n}\r\n```\r\n\r\nAgain, the `WrapperValueObject\u003cstring\u003e` base class has become optional, as the generated partial class implements it.\r\n\r\nTo also have comparison methods generated, the `IComparable\u003cDescription\u003e` interface can optionally be added, if the type is considered to have a natural order.\r\n\r\n### Entity\r\n\r\nAn entity is a data model that is defined by its identity and a thread of continuity. It may be mutated during its life cycle. Entities are often stored in a database.\r\n\r\nFor entities themselves, the package offers base types, with no source generation required. However, it is often desirable to have a custom type for an entity's ID. For example, `PaymentId` tends to be a more expressive type than `ulong`. Unfortunately, such custom ID types tend to consist of boilerplate code that gets in the way, is a hassle to write, and is easy to make mistakes in.\r\n\r\nConsider the following type:\r\n\r\n```cs\r\n[Entity]\r\npublic class Payment : Entity\u003cPaymentId\u003e\r\n{\r\n\tpublic string Currency { get; }\r\n\tpublic decimal Amount { get; }\r\n\r\n\tpublic Payment(string currency, decimal amount)\r\n\t\t: base(new PaymentId())\r\n\t{\r\n\t\tthis.Currency = currency ?? throw new ArgumentNullException(nameof(currency));\r\n\t\tthis.Amount = amount;\r\n\t}\r\n}\r\n```\r\n\r\nThe entity needs a `PaymentId` type. This type could be a full-fledged `WrapperValueObject\u003culong\u003e` or `WrapperValueObject\u003cstring\u003e`, with `IComparable\u003cPaymentId\u003e`.\r\nIn fact, it might also be desirable for such a type to be a struct.\r\n\r\nChange the type as follows to get a source-generated ID type for the entity:\r\n\r\n```cs\r\n[Entity]\r\npublic class Payment : Entity\u003cPaymentId, string\u003e\r\n{\r\n\t// Snip\r\n}\r\n```\r\n\r\nThe `Entity\u003cTId, TIdPrimitive\u003e` base class is what triggers source generation of the `TId`, if no such type exists.\r\nThe `TIdPrimitive` type parameter specifies the underlying primitive to use.\r\nUsing this base class to have the ID type generated is equivalent to [manually declaring one](#identity).\r\n\r\nWhen entities share a custom base class, such as in a scenario with a `Banana` and a `Strawberry` entity each inheriting from `Fruit`, then it is possible to have `Fruit` inherit from `Entity\u003cFruitId, TPrimitive\u003e`, causing `FruitId` to be generated.\r\nThe `[Entity]` attribute, however, should only be applied to the concrete types, `Banana` and `Strawberry`'.\r\n\r\nFurthermore, the above example entity could be modified to create a new, unique ID on construction:\r\n\r\n```cs\r\npublic Payment(string currency, decimal amount)\r\n\t: base(new PaymentId(Guid.NewGuid().ToString(\"N\")))\r\n{\r\n\t// Snip\r\n}\r\n```\r\n\r\nFor a more database-friendly alternative to UUIDs, see [Distributed IDs](https://github.com/TheArchitectDev/Architect.Identities#distributed-ids).\r\n\r\n### Identity\r\n\r\nIdentity types are a special case of value objects. Unlike other value objects, they are perfectly suitable to be implemented as structs:\r\n\r\n- The enforced default constructor is unproblematic, because there is hardly such a thing as an invalid ID value. Although ID 0 or -1 might not _exist_, the same might be true for ID 999999, which would still be valid as a value.\r\n- The possibility of an ID variable containing `null` is often undesirable. Structs avoid this complication. (Where we _want_ nullability, a nullable struct can be used, e.g. `PaymentId?`.\r\n- If the underlying type is `string`, the generator ensures that its `Value` property returns the empty string instead of `null`. This way, even `string`-wrapping identities know only one \"empty\" value and avoid representing `null`.\r\n\r\nSince an application is expected to work with many ID instances, using structs for them is a nice optimization that reduces heap allocations.\r\n\r\nSource-generated identities implement both `IEquatable\u003cT\u003e` and `IComparable\u003cT\u003e` automatically. They are declared as follows:\r\n\r\n```cs\r\n[Identity\u003culong\u003e]\r\npublic readonly partial struct PaymentId : IIdentity\u003culong\u003e\r\n{\r\n}\r\n```\r\n\r\nFor even terser syntax, we can omit the interface and the `readonly` keyword (since they are generated), and even use a `record struct` to omit the curly braces:\r\n\r\n```cs\r\n[Identity\u003cstring\u003e]\r\npublic partial record struct ExternalId;\r\n```\r\n\r\nNote that an [entity](#entity) has the option of having its own ID type generated implicitly, with practically no code at all.\r\n\r\n### Domain Event\r\n\r\nThere are many ways of working with domain events, and this package does not advocate any particular one. As such, no interfaces, base types, or source generators are included that directly implement domain events.\r\n\r\nTo mark domain event types as such, regardless of how they are implemented, the `[DomainEvent]` attribute can be used:\r\n\r\n```cs\r\n[DomainEvent]\r\npublic class OrderCreatedEvent : // Snip\r\n```\r\n\r\nBesides providing consistency, such a marker attribute can enable miscellaneous concerns. For example, if the package's Entity Framework mappings are used, domain events can be included.\r\n\r\n### DummyBuilder\r\n\r\nDomain objects have parameterized constructors, so that they can guarantee a valid state. For many value objects, such constructors are unlikely to ever change: `new PaymentId(1)`, `new Description(\"Example\")`, `new Color(1, 1, 1)`.\r\n\r\nHowever, entities have constructors that tend to change. The same applies for value objects that exist simply to group clusters of an entity's properties, e.g. `PaymentConfiguration`. When one of these constructors changes, such as when the `Payment` entity gets a new property (one that should be passed to the constructor), then all the callers need to change accordingly.\r\n\r\nUsually, production code has just a handful of callers of an entity's constructor. However, _test code_ can easily have dozens of callers of that constructor.\r\n\r\nThe simple act of adding one property would require dozens of additional changes instead of a handful, only because of the existence of test code. The changes are \"dumb\" changes, as the test methods do not care about the new property, which never existed when they were written.\r\n\r\nThe Builder pattern fixes this problem:\r\n\r\n```cs\r\npublic class PaymentDummyBuilder\r\n{\r\n\t// Have a default value for each property, along with a fluent method to change it\r\n\r\n\tprivate string Currency { get; set; } = \"EUR\";\r\n\tpublic PaymentDummyBuilder WithCurrency(string value) =\u003e this.With(b =\u003e b.Currency = value);\r\n\r\n\tprivate decimal Amount { get; set; } = 1.00m;\r\n\tpublic PaymentDummyBuilder WithAmount(decimal value) =\u003e this.With(b =\u003e b.Amount = value);\r\n\t\r\n\tprivate PaymentDummyBuilder With(Action\u003cPaymentDummyBuilder\u003e assignment)\r\n\t{\r\n\t\tassignment(this);\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// Have a Build() method to invoke the most usual constructor with the configured values\r\n\r\n\tpublic override Payment Build()\r\n\t{\r\n\t\tvar result = new Payment(\r\n\t\t\tcurrency: this.Currency,\r\n\t\t\tamount: this.Amount);\r\n\t\treturn result;\r\n\t}\r\n}\r\n```\r\n\r\nTest methods avoid constructor invocations, e.g. `new Payment(\"EUR\", 1.00m)`, and instead use the following:\r\n\r\n```cs\r\nnew PaymentBuilder().Build(); // Completely default instance\r\nnew PaymentBuilder().WithCurrency(\"USD\").Build(); // Partially modified instance for a specific test\r\n```\r\n\r\nFor example, to test that the constructor throws the appropriate exception when given a null currency:\r\n\r\n```cs\r\nAssert.Throws\u003cArgumentNullException\u003e(() =\u003e new PaymentBuilder().WithCurrency(null!).Build());\r\n```\r\n\r\nThis way, whenever a constructor is changed, the only test code that breaks is the dummy builder. Instead of dozens of additional changes, we need only make a handful.\r\n\r\nAs the builder is repaired to account for the changed constructor, all tests work again. If a new constructor parameter was added, existing tests tend to work perfectly fine as long as the builder provides a sensible default value for the parameter.\r\n\r\nUnfortunately, the dummy builders tend to consist of boilerplate code and can be tedious to write and maintain.\r\n\r\nChange the type as follows to get source generation for it:\r\n\r\n```cs\r\n[DummyBuilder\u003cPayment\u003e]\r\npublic partial class PaymentDummyBuilder\r\n{\r\n\t// Anything defined manually will cause the source generator to outcomment its conflicting code, i.e. manual code always takes precedence\r\n\r\n\t// The source generator is fairly good at instantiating default values, but not everything it generates is sensible to the domain model\r\n\t// Since the source generator cannot guess what an example currency value might look like, we define that property and its initializer manually\r\n\t// Everything else we let the source generator provide\r\n\r\n\tprivate string Currency { get; set; } = \"EUR\";\r\n}\r\n```\r\n\r\nThe generated `Build()` method opts for _the most visible, simplest parameterized constructor_, since it tends to represent the most \"regular\" way of constructing the domain object. Specifically, it picks by { greatest visibility, parameterized over default, fewest parameters }. The builder's properties and fluent methods are based on that same constructor. We can deviate by manually implementing the `Build()` method and manually adding properties and fluent methods. To remove generated fluent methods, we can obscure them by manually implementing them as private, protected, or internal.\r\n\r\nDummy builders generally live in a test project, or in a library project consumed solely by test projects.\r\n\r\n## Constructor Validation\r\n\r\nDDD promotes the validation of domain rules and invariants in the constructors of the domain objects. This pattern is fully supported:\r\n\r\n```cs\r\npublic const ushort MaxLength = 255;\r\n\r\npublic Description(string value)\r\n{\r\n\tthis.Value = value ?? throw new ArgumentNullException(nameof(value));\r\n\r\n\tif (this.Value.Length == 0) throw new ArgumentException($\"A {nameof(Description)} must not be empty.\");\r\n\tif (this.Value.Length \u003e MaxLength) throw new ArgumentException($\"A {nameof(Description)} must not be over {MaxLength} characters long.\");\r\n\tif (ContainsNonPrintableCharacters(this.Value, flagNewLinesAndTabs: false)) throw new ArgumentException($\"A {nameof(Description)} must contain only printable characters.\");\r\n}\r\n```\r\n\r\nAny type that inherits from `ValueObject` also gains access to a set of (highly optimized) validation helpers, such as `ContainsNonPrintableCharacters()` and `ContainsNonAlphanumericCharacters()`.\r\n\r\n### Construct Once\r\n\r\nFrom the domain model's perspective, any instance is constructed only once. The domain model does not care if it is serialized to JSON or persisted in a database before being reconstituted in main memory. The object is considered to have lived on.\r\n\r\nAs such, constructors in the domain model should not be re-run when objects are reconstituted. The source generators provide this property:\r\n\r\n- Each generated `IIdentity\u003cT\u003e` and `WrapperValueObject\u003cTValue\u003e` comes with a JSON converter for both System.Text.Json and Newtonsoft.Json, each of which deserialize without the use of (parameterized) constructors.\r\n- Each generated `ValueObject` will have an empty default constructor for deserialization purposes, with a `[JsonConstructor`] attribute for both System.Text.Json and Newtonsoft.Json. Declare its properties with `private init` and add a `[JsonInclude]` and `[JsonPropertyName(\"StableName\")]` attribute to allow them to be rehydrated.\r\n- If the generated [Entity Framework mappings](#entity-framework-conventions) are used, all domain objects are reconstituted without the use of (parameterized) constructors.\r\n- Third party extensions can use the methods on `DomainObjectSerializer` to (de)serialize according to the same conventions.\r\n\r\n## Serialization\r\n\r\nFirst and foremost, serialization of domain objects for _public_ purposes should be avoided.\r\nTo expose data outside of the bounded context, create separate contracts and adapters to convert back and forth.\r\nIt is advisable to write such adapters manually, so that a compiler error occurs when changes to either end would break the adaptation.\r\n\r\nSerialization inside the bounded context is useful, such as for persistence, be it in the form of JSON documents or in relational database tables.\r\n\r\n### Identity and WrapperValueObject Serialization\r\n\r\nThe generated JSON converters and Entity Framework mappings (optional) end up calling the generated `Serialize` and `Deserialize` methods, which are fully customizable.\r\nDeserialization uses the default constructor and the value property's initializer (`{ get; private init }`).\r\nFallbacks are in place in case a value property was manually declared with no initializer.\r\n\r\n### ValueObject Serialization\r\n\r\nGenerated value object types have a private, empty default constructor intended solely for deserialization. System.Text.Json, Newtonsoft.Json, and Entity Framework each prefer this constructor.\r\n\r\nValue object properties should be declared as `{ get; private init; }`. If no initializer is provided, the included analyzer emits a warning, since properties may not be deserializable.\r\n\r\nIf a value object is ever serialized to JSON, its properties should have the `[JsonInclude]` attribute.\r\nSince renaming a property would break any existing JSON blobs, it is advisable to hardcode a property name for use in JSON through the `[JsonPropertyName(\"StableName\")]`.\r\nAvoid `nameof()`, so that JSON serialization is unaffected by future renames.\r\n\r\nFor Entity Framework, when storing a complex value object directly into an entity's table, prefer the `ComplexProperty()` feature (either with or without `ToJson()`).\r\nProperty renames for individual columns are handled by migrations. Property renames inside JSON blobs are covered by earlier paragraphs.\r\n\r\nAt the time of writing, Entity Framework's `ComplexProperty()` does [not yet](https://github.com/dotnet/efcore/issues/31252) combine with `ToJson()`, necessitating manual JSON serialization.\r\n\r\n### Entity and Domain Event Serialization\r\n\r\nIf an entity or domain event is ever serialized to JSON, it is up to the developer to provide an empty default constructor, since there is no other need to generate source for these types.\r\nThe `[Obsolete]` attribute and `private` accessibility can be used to prevent a constructor's unintended use.\r\n\r\nIf the generated [Entity Framework mappings](#entity-framework-conventions) are used, entities and/or domain objects can be reconstituted entirely without the use of constructors, thus avoiding the need to declare empty default constructors.\r\n\r\n## Entity Framework Conventions\r\n\r\nConventions to provide Entity Framework mappings are generated on-demand, only if any override of `ConfigureConventions(ModelConfigurationBuilder)` is declared.\r\nThere are no hard dependencies on Entity Framework, nor is there source code overhead in its absence.\r\nIt is up to the developer which conventions, if any, to use.\r\n\r\n```cs\r\ninternal sealed class MyDbContext : DbContext\r\n{\r\n\t// Snip\r\n\r\n\tprotected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)\r\n\t{\r\n\t\t// Recommended to keep EF from throwing if it sees no usable constructor, if we are keeping it from using constructors anyway\r\n\t\tconfigurationBuilder.Conventions.Remove\u003cConstructorBindingConvention\u003e();\r\n\r\n\t\tconfigurationBuilder.ConfigureDomainModelConventions(domainModel =\u003e\r\n\t\t{\r\n\t\t\tdomainModel.ConfigureIdentityConventions();\r\n\t\t\tdomainModel.ConfigureWrapperValueObjectConventions();\r\n\t\t\tdomainModel.ConfigureEntityConventions();\r\n\t\t\tdomainModel.ConfigureDomainEventConventions();\r\n\t\t});\r\n\t}\r\n}\r\n```\r\n\r\n`ConfigureDomainModelConventions()` itself does not have any effect other than to invoke its action, which allows the specific mapping kinds to be chosen.\r\nThe inner calls, such as to `ConfigureIdentityConventions()`, configure the various conventions.\r\n\r\nThanks to the provided conventions, no manual boilerplate mappings are needed, like conversions to primitives.\r\nThe developer need only write meaningful mappings, such as the maximum length of a string property.\r\n\r\nSince only conventions are registered, regular mappings can override any part of the provided behavior.\r\n\r\nThe conventions map each domain object type explicitly and are trimmer-safe.\r\n\r\n## Third-Party Mappings\r\n\r\nIf there are other concerns than Entity Framework that need to map each domain object, they can benefit from the same underlying mechanism.\r\nFor example, JSON mappings for additional JSON libraries could made.\r\n\r\nA concrete configurator can be created implementing `IEntityConfigurator`, `IDomainEventConfigurator`, `IIdentityConfigurator`, or `IWrapperValueObjectConfigurator`.\r\nFor example, to log each concrete entity type:\r\n\r\n```cs\r\npublic sealed clas LoggingEntityConfigurator : Architect.DomainModeling.Configuration.IEntityConfigurator\r\n{\r\n\t// Note: The attributes on the type parameter may look complex, but are provided by the IDE when implementing the interface\r\n\tpublic void ConfigureEntity\u003c[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] TEntity\u003e(\r\n\t\tin Architect.DomainModeling.Configuration.IEntityConfigurator.Args args)\r\n\t\twhere TEntity : IEntity\r\n\t{\r\n\t\tConsole.WriteLine($\"Registered entity {typeof(TEntity).Name}.\");\r\n\t}\r\n}\r\n```\r\n\r\nThe `ConfigureEntity()` method can then be invoked once for each annotated entity type as follows:\r\n\r\n```cs\r\nvar entityConfigurator = new LoggingEntityConfigurator();\r\n\r\nMyDomainLayerAssemblyName.EntityDomainModelConfigurator.ConfigureEntities(entityConfigurator);\r\n\r\n// If we have multiple assemblies containing entities\r\nMyOtherDomainLayerAssemblyName.EntityDomainModelConfigurator.ConfigureEntities(entityConfigurator);\r\n```\r\n\r\nThe static `EntityDomainModelConfigurator` (and corresponding types for the other kinds of domain object) is generated once for each assembly that contains such domain objects.\r\nIts `ConfigureEntities(IEntityConfigurator)` method calls back into the given configurator, once for each annotated entity type in the assembly.\r\n\r\n## Structural Equality\r\n\r\nValue objects (including identities and wrappers) should have structural equality, i.e. their equality should depend on their contents.\r\nFor example, `new Color(1, 1, 1) == new Color(1, 1, 1)` should evaluate to `true`.\r\nThe source generators provide this for all `Equals()` overloads and for `GetHashCode()`.\r\nWhere applicable, `CompareTo()` is treated the same way.\r\n\r\nThe provided structural equality is non-recursive: a value object's properties are expected to each be of a type that itself provides structural equality, such as a primitive, a `ValueObject`, a `WrapperValueObject\u003cTValue\u003e`, or an `IIdentity\u003cT\u003e`.\r\n\r\nThe generators also provide structural equality for members that are of collection types, by comparing the elements.\r\nEven nested collections are account for, as long as the nesting is direct, e.g. `int[][]`, `Dictionary\u003cint, List\u003cstring\u003e\u003e`, or `int[][][]`.\r\nFor `CompareTo()`, a structural implementation for collections is not supported, and the generators will skip `CompareTo()` if any property lacks the `IComparable\u003cTSelf\u003e` interface.\r\n\r\nThe logic for structurally comparing collection types is made publicly available through the `EnumerableComparer`, `DictionaryComparer`, and `LookupComparer` types.\r\n\r\nThe collection equality checks inspect and compare the collections as efficiently as possible.\r\nOptimized paths are in place for common collection types.\r\nSets, being generally order-agnostic, are special-cased: they dictate their own comparers; a set is never equal to a non-set (unless both are null or both are empty); two sets are equal if each considers the other to contain all of its elements.\r\nDictionary and lookup equality is similar to set equality when it comes to their keys.\r\n\r\nFor the sake of completeness, the collection comparers also provide overloads for the non-generic `IEnumerable`.\r\nThese should be avoided.\r\nWorking with non-generic enumerables tends to be inefficient due to virtual calls and boxing.\r\nThese overloads work hard to return identical results to the generic overloads, at additional costs to efficiency.\r\n\r\n## Testing\r\n\r\n### Generated Files\r\n\r\nWhile \"Go To Definition\" works for inspecting source-generated code, sometimes you may want to have the generated code in files.\r\nTo have source generators write a copy to a file for each generated piece of code, add the following to the project file containing your source-generated types and find the files in the `obj` directory:\r\n\r\n```xml\r\n\u003cPropertyGroup\u003e\r\n\t\u003cEmitCompilerGeneratedFiles\u003eTrue\u003c/EmitCompilerGeneratedFiles\u003e\r\n\t\u003cCompilerGeneratedFilesOutputPath\u003e$(BaseIntermediateOutputPath)/GeneratedFiles\u003c/CompilerGeneratedFilesOutputPath\u003e\r\n\u003c/PropertyGroup\u003e\r\n```\r\n\r\n### Debugging\r\n\r\nSource generators can be debugged by enabling the following (outcommented) line in the `DomainModeling.Generator` project. To start debugging, rebuild and choose the current Visual Studio instance in the dialog that appears.\r\n\r\n```cs\r\nif (!System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Launch();\r\n```\r\n","funding_links":[],"categories":["Content","Source Generators"],"sub_categories":["124. [Architect.DomainModeling](https://ignatandrei.github.io/RSCG_Examples/v2/docs/Architect.DomainModeling) , in the [Builder](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#builder) category","Domain Driven Design (DDD)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTheArchitectDev%2FArchitect.DomainModeling","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FTheArchitectDev%2FArchitect.DomainModeling","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FTheArchitectDev%2FArchitect.DomainModeling/lists"}