Skip to content

Domain Base Entities

Nataraja edited this page Jun 13, 2024 · 3 revisions

CleanCore Domain Base Entities Documentation

Welcome to the documentation of the CleanCore Domain Base Entities, a cornerstone in the Domain-Driven Design (DDD) architecture. These abstract classes are designed to provide a common structure and essential functionalities to all domain entities, promoting consistency, maintainability, and scalability in applications.

BaseEntity: The Foundation

The BaseEntity class represents the foundational entity from which all other domain entities should inherit. It provides common functionalities such as domain event management, audit information, and soft deletion.

Domain Event Management

Domain events are used to implement communication between entities in a loosely coupled manner. The BaseEntity includes a list of domain events that can be added or removed by derived entities.

public void AddDomainEvent(BaseEntityEvent domainEvent)
{
    _domainEvents.Add(domainEvent);
}

public void RemoveDomainEvent(BaseEntityEvent domainEvent)
{
    _domainEvents.Remove(domainEvent);
}

Audit Information and Soft Delete

Audit information such as CreatedAt, CreatedBy, LastModifiedAt, LastModifiedBy, DeletedAt, and DeletedBy are essential for tracking changes to entities. Soft deletion is implemented via the IsDeleted property, indicating whether an entity has been marked as deleted without physically removing it from the database.

public virtual void Deleted(string? by = "system", DateTimeOffset? when = null)
{
    DeletedAt = when ?? DateTimeOffset.Now;
    DeletedBy = by;
}

BaseEntity<TId, TEntity, TDto>: Generic Extension

The BaseEntity<TId, TEntity, TDto> class extends BaseEntity by introducing support for generic type identifiers, validation, and DTO mapping.

Validation

Entity validation is crucial for maintaining domain integrity. BaseEntity<TId, TEntity, TDto> requires derived classes to implement the Validate method, which should return a result indicating whether the entity is in a valid state.

public abstract ErrorOr<TEntity> Validate();

DTO Mapping

Mapping between entities and DTOs (Data Transfer Objects) is a common practice in DDD for transferring data between application layers. BaseEntity<TId, TEntity, TDto> facilitates this mapping by providing an abstract method ToDto that must be implemented by derived entities.

public abstract TDto ToDto();

BaseEntityAndDto<TId, TEntity>: Merging Entity and DTO

The BaseEntityAndDto<TId, TEntity> class represents an innovation in design, combining entity and DTO into a single class. This approach reduces code duplication and simplifies the mapping between entities and DTOs.

Usage Example

Suppose we have a User entity that inherits from BaseEntityAndDto<int, User> and we want to update the user's information and generate a DTO for data transfer.

public class User : BaseEntityAndDto<int, User>
{
    public string Name { get; set; }

    public override User ToDto()
    {
        return this;
    }

    public override ErrorOr<User> Validate()
    {
        if (string.IsNullOrEmpty(Name))
        {
            return Error.Validation(description: "Name is required.");
        }

        return this;
    }
}

Advantages of Using Generic IDs in BaseEntity

The use of generic IDs in the BaseEntity<TId, TEntity, TDto> class provides several advantages in designing and implementing domain entities in a Domain-Driven Design (DDD) architecture. This approach enhances flexibility, type safety, and compatibility across different types of data stores. Below are three examples illustrating the benefits of using generic IDs with different data types such as int and string.

1. Flexibility with Different ID Types

Using generic IDs allows for flexibility in choosing the type of the ID based on the requirements of the domain or the data store. For instance, a relational database might use an int as the primary key, while a document database might use a string (UUID) as the document ID.

Example with an Integer ID:

public class User : BaseIntEntity<User, UserDto>
{
    // User-specific properties
}

In this example, the User entity inherits from BaseIntEntity, which is a specialized version of BaseEntity that uses an int as the ID type. This is suitable for scenarios where the ID is an auto-incremented integer, commonly used in relational databases.

Example with a String ID:

public class Document : BaseEntity<string, Document, DocumentDto>
{
    // Document-specific properties
}

Here, the Document entity uses a string as the ID type, which can accommodate UUIDs or other string-based identifiers. This is particularly useful for document databases or scenarios where the ID is generated in a distributed system.

2. Type Safety

Generic IDs enforce type safety at compile time, ensuring that entities and their related operations (such as CRUD operations) use the correct type of ID. This reduces the risk of runtime errors and bugs related to ID type mismatches.

Example:

public class Order : BaseIntEntity<Order, OrderDto>
{
    public void AddItem(OrderItem item)
    {
        // Implementation
    }
}

public class OrderItem : BaseIntEntity<OrderItem, OrderItemDto>
{
    // OrderItem-specific properties
}

In this example, both Order and OrderItem entities use an int as their ID type. The type safety provided by generic IDs ensures that operations involving these entities are consistent with their ID types.

3. Compatibility with Different Data Stores

Generic IDs make the BaseEntity class compatible with various data stores, each of which might have different requirements or conventions for IDs. This compatibility facilitates the use of the same domain model across different types of databases without changing the entity definitions.

Example with a GUID ID:

public class Session : BaseEntity<Guid, Session, SessionDto>
{
    // Session-specific properties
}

In this example, the Session entity uses a Guid as the ID type, which is a common choice for distributed systems or scenarios where globally unique identifiers are required. This demonstrates the adaptability of the BaseEntity class to different data storage requirements.

In conclusion, the use of generic IDs in the BaseEntity class offers significant advantages in terms of flexibility, type safety, and compatibility with different data stores. These benefits contribute to a more robust and adaptable domain model in a DDD architecture.

Understanding BaseEntityEvent in CleanCore.Domain

In the grand tapestry of software architecture, the BaseEntityEvent class within the CleanCore.Domain framework emerges as a pivotal thread, weaving together the fabric of domain-driven design with the robustness of event-driven architectures. This document endeavors to unravel the intricacies of BaseEntityEvent, presenting a narrative that is both creatively rich and professionally detailed.

The Prologue: BaseEntityEvent's Role

At its core, BaseEntityEvent serves as the foundational class for all domain events within the CleanCore.Domain framework. It is the herald of change, notifying the system of significant occurrences within the domain entities, thereby facilitating a loosely coupled and highly cohesive system design.

The Cast: Generic Parameters

The BaseEntityEvent class is not a solitary actor but rather a versatile protagonist, capable of adapting to various roles through its generic parameters:

  • TId: Represents the type of the entity's identifier, ensuring flexibility across different identification schemes.
  • TEntity: The entity type that the event pertains to, allowing for specific event handling.
  • TDto: The Data Transfer Object associated with the entity, facilitating the transfer of data across system boundaries.

The Plot: Initialization and Usage

The BaseEntityEvent class is instantiated with a specific entity, encapsulating the event's context. This design choice ensures that each event carries with it the entity it affects, allowing event handlers to react appropriately.

public BaseEntityEvent(Order order)
{
Entity = order;
}

In this snippet, an order-related event is created, encapsulating the Order entity within the event. This allows subscribers to the event to access the order details directly from the event instance.

The Climax: Integration with MediatR

Integration with the MediatR library elevates BaseEntityEvent to a new level of significance. By implementing the INotification interface, BaseEntityEvent becomes a conduit for notifications within the MediatR pipeline, enabling a seamless and elegant way to handle domain events.

The Denouement: A Symphony of Events

The true power of BaseEntityEvent lies in its ability to orchestrate a symphony of events, each resonating with the changes within the domain. It is a testament to the elegance of event-driven architectures, where each event contributes to the narrative of the domain, enriching the application with a dynamic and responsive design.

Epilogue

The journey through the realm of BaseEntityEvent reveals a landscape where domain-driven design and event-driven architectures converge. Through creative examples and professional discourse, we have explored the essence of BaseEntityEvent, uncovering its role as a cornerstone in the architecture of modern, responsive applications.

Conclusion

The CleanCore Domain Base Entities offer a solid foundation for building applications following DDD principles. They provide common functionalities and promote consistent design practices, facilitating the development of expressive and maintainable domain models. The use of generic base classes and the merging of entities and DTOs further simplify domain entity management, making the code cleaner and less prone to errors.