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
A shared kernel for DDD-based .NET applications. Provides battle-tested abstractions for Clean Architecture projects: Entity, ValueObject, Result pattern, Domain Events, Specification pattern, and Pagination.
Why this exists
Every enterprise .NET project I build needs the same foundational types. Instead of copying them across repositories, this package is the single source of truth.
Design principles:
Zero external dependencies in the main library
Immutable by default — ValueObject, PagedList, Error are all sealed/records
Composable — Result supports Map/Bind; Specification supports And/Or/Not with &, |, ! operators
No magic — every abstraction is readable and debuggable without a framework
Installation
dotnet add package EricksonLopez.SharedKernel
Quick Start
Result Pattern
// Define errors in a static class per domain conceptpublicstaticclassUserErrors{publicstaticErrorNotFound(Guidid)=>Error.NotFound("User.NotFound",$"User '{id}' was not found.");publicstaticreadonlyErrorNameEmpty=Error.Validation("User.NameEmpty","Name cannot be empty.");}// Return Result instead of throwingpublicResult<User>GetUser(Guidid){varuser=_repository.Find(id);returnuserisnull?UserErrors.NotFound(id):user;}// Caller handles explicitlyvarresult=GetUser(id);if(result.IsFailure)returnresult.Error;// propagate up// Monadic chainingvarresult=GetUser(id).Map(u =>newUserDto(u.Name,u.Email)).Bind(dto =>Validate(dto));
publicsealedclassOrder:Entity<Guid>{privateOrder(){}publicstringDescription{get;privateset;}=string.Empty;publicstaticOrderCreate(stringdescription){varorder=newOrder{Id=Guid.NewGuid(),Description=description};order.RaiseDomainEvent(newOrderCreatedEvent(order.Id));returnorder;}}// Domain eventpublicsealedrecordOrderCreatedEvent(GuidOrderId):IDomainEvent;// In your Unit of Work / repository — after SaveChanges:foreach(varentityinentities){varevents=entity.DomainEvents.ToList();entity.ClearDomainEvents();foreach(vardomainEventinevents)await_publisher.Publish(domainEvent,cancellationToken);}
Specification Pattern
publicsealedclassActiveUserSpec:Specification<User>{publicoverrideExpression<Func<User,bool>>ToExpression()=> user =>user.IsActive;}publicsealedclassPremiumUserSpec:Specification<User>{publicoverrideExpression<Func<User,bool>>ToExpression()=> user =>user.Tier==UserTier.Premium;}// Composevarspec=newActiveUserSpec()&newPremiumUserSpec();// In-memory evaluationvareligibleUsers=users.Where(spec.IsSatisfiedBy);// As LINQ expression (for Dapper/EF)varexpression=spec.ToExpression();// Expression<Func<User, bool>>
Pagination
// In your query handlerpublicasyncTask<PagedList<ProductDto>>Handle(GetProductsQueryquery){varparameters=PaginationParameters.Of(query.Page,query.PageSize);varitems=await_connection.QueryAsync<ProductDto>("SELECT * FROM products LIMIT @limit OFFSET @offset",new{limit=parameters.PageSize,offset=parameters.Skip});vartotal=await_connection.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM products");returnPagedList<ProductDto>.Create(items,total,parameters);}// PagedList<T> metadatapage.TotalCount// Total items across all pages
page.TotalPages // Ceiling(TotalCount / PageSize)
page.HasNextPage // true if not on last page
page.Map(dto=>newProductResponse(dto.Id,dto.Name))// project without losing metadata