modules/ai-guide/Write-AiGuide-Domain.ps1
|
param( [string] $ProjectName, [string] $OutputPath, [string[]] $Entities = @("User", "Product") ) . (Join-Path (Split-Path $MyInvocation.MyCommand.Path) "..\core\Utils.ps1") $guideDir = Join-Path $OutputPath "ai-guide" function Write-Guide($fileName, $content) { Write-CoreFile (Join-Path $guideDir $fileName) $content } $allEntitiesStr = $Entities -join ", " $domainGuideContent = @' # Domain Layer AI Developer Guide You are an AI assistant helping developers build the Domain Layer of the {P} solution. This layer contains the enterprise business rules, entities, aggregate roots, value objects, and repository interfaces. --- ## Strict Domain Layer Rules 1. **No External Dependencies:** The Domain project must be 100% pure C#. Do not import packages from other layers, database libraries, or web packages. 2. **Encapsulation:** Properties on entities must use private or protected setters (`get; private set;` or `get; protected set;`). Avoid public setters. 3. **Rich Domain Models:** Do not create anemic entities. All changes to the entity state should occur through explicit domain methods (e.g. `ChangeEmail(Email email)`, `Activate()`). 4. **Aggregate Roots:** Key standalone entities are Aggregate Roots. They must implement the `IAggregateRoot` marker interface. 5. **Value Objects:** Use immutable Value Objects (inheriting from `ValueObject.cs`) for complex types that do not have identities but have business rules (e.g., `Email`). 6. **Self-Validation:** Validate invariants in constructor and domain methods. Throw exceptions (like `ArgumentException`) on invalid values. --- ## Existing Entities The following entities are already scaffolded in this project: **{Entities}** Use them as reference patterns when adding new entities. - **User** - Full DDD Aggregate Root with `Email` Value Object. Methods: `UpdateRole()`, `ChangeEmail()`, `Activate()`, `Deactivate()`. - **Product** - Rich Domain Model. Methods: `UpdateDetails(name, price)`. --- ## Common Base Types ### 1. BaseEntity.cs All entities inherit from `BaseEntity`. It encapsulates identity, creation, update, and soft-delete states: ```csharp public abstract class BaseEntity { public int Id { get; protected set; } public DateTime CreatedAt { get; protected set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; protected set; } = DateTime.UtcNow; public bool IsDeleted { get; protected set; } = false; public DateTime? DeletedAt { get; protected set; } public virtual void Delete() { if (!IsDeleted) { IsDeleted = true; DeletedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } } } ``` ### 2. ValueObject.cs Provides base comparison logic for structural equality: ```csharp public abstract class ValueObject { protected abstract IEnumerable<object?> GetEqualityComponents(); public override bool Equals(object? obj) { if (obj == null || obj.GetType() != GetType()) return false; var other = (ValueObject)obj; return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); } public override int GetHashCode() { return GetEqualityComponents() .Select(x => x != null ? x.GetHashCode() : 0) .Aggregate((x, y) => x ^ y); } } ``` ### 3. IAggregateRoot.cs Marker interface to identify Aggregate Roots: ```csharp public interface IAggregateRoot { } ``` --- ## How to Add/Modify a Domain Model When asked to add a new entity (e.g., `Category`), write it as a Rich Domain Model: ### 1. Entity Implementation ```csharp // {P}-backend/src/{P}.Domain/Entities/Category.cs using {P}.Domain.Common; namespace {P}.Domain.Entities; public class Category : BaseEntity, IAggregateRoot { public string Name { get; private set; } = string.Empty; // Required for ORM mapping (EF Core & Dapper) protected Category() { } // Invariant-enforcing constructor public Category(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name cannot be empty.", nameof(name)); Name = name.Trim(); CreatedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } // State change method public void UpdateDetails(string name) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Name cannot be empty.", nameof(name)); Name = name.Trim(); UpdatedAt = DateTime.UtcNow; } } ``` ### 2. Repository Interface Create a dedicated repository interface in the `Interfaces/` folder: ```csharp // {P}-backend/src/{P}.Domain/Interfaces/ICategoryRepository.cs using {P}.Domain.Common; using {P}.Domain.Entities; namespace {P}.Domain.Interfaces; public interface ICategoryRepository : IRepository<Category> { // Add custom query methods here if needed } ``` '@.Replace("{P}", $ProjectName).Replace("{Entities}", $allEntitiesStr) Write-Guide "domain-guide.md" $domainGuideContent |