Skip to content

Composite

The Composite pattern allows treating individual objects and compositions of objects uniformly. In Granit, this pattern manifests in the auditable entity hierarchy where each level adds capabilities while remaining uniformly manipulable.

classDiagram
    class Entity {
        +Id : Guid
    }

    class CreationAuditedEntity {
        +CreatedAt : DateTimeOffset
        +CreatedBy : string
    }

    class AuditedEntity {
        +ModifiedAt : DateTimeOffset?
        +ModifiedBy : string?
    }

    class FullAuditedEntity {
        +IsDeleted : bool
        +DeletedAt : DateTimeOffset?
        +DeletedBy : string?
    }

    class ISoftDeletable {
        <<interface>>
    }

    class IMultiTenant {
        +TenantId : Guid?
    }

    class IActive {
        +IsActive : bool
    }

    Entity <|-- CreationAuditedEntity
    CreationAuditedEntity <|-- AuditedEntity
    AuditedEntity <|-- FullAuditedEntity
    FullAuditedEntity ..|> ISoftDeletable
ClassFileAdded capabilities
Entitysrc/Granit.Core/Domain/Entity.csId (Guid)
CreationAuditedEntitysrc/Granit.Core/Domain/CreationAuditedEntity.csCreatedAt, CreatedBy
AuditedEntitysrc/Granit.Core/Domain/AuditedEntity.csModifiedAt, ModifiedBy
FullAuditedEntitysrc/Granit.Core/Domain/FullAuditedEntity.csIsDeleted, DeletedAt, DeletedBy (ISoftDeletable)
ISoftDeletablesrc/Granit.Core/Domain/ISoftDeletable.csSoft delete marker
IMultiTenantsrc/Granit.Core/Domain/IMultiTenant.csTenantId isolation
IActivesrc/Granit.Core/Domain/IActive.csIsActive filtering

The marker interfaces (ISoftDeletable, IMultiTenant, IActive) are composable with the inheritance hierarchy. EF Core interceptors and query filters detect these interfaces via reflection and apply the appropriate behavior.

The progressive hierarchy allows choosing the required audit level per entity. A reference entity (postal code) only needs Entity. A medical entity under ISO 27001 needs FullAuditedEntity + IMultiTenant. The interceptors treat all entities uniformly.

// Simple entity -- identity only
public sealed class Country : Entity { }
// Entity with creation audit
public sealed class Invitation : CreationAuditedEntity { }
// Entity with full ISO 27001 audit + tenant isolation + GDPR soft delete
public sealed class MedicalRecord : FullAuditedEntity, IMultiTenant
{
public Guid? TenantId { get; set; }
public string Diagnosis { get; set; } = string.Empty;
}
// Interceptors automatically populate all audit fields