Repositories¶
Info
At ConnectSoft, Repositories are the gateways between the pure domain model
and the persistence infrastructure — enabling clean architecture, testability, and resilient domain evolution.
Introduction¶
In Domain-Driven Design (DDD), a Repository acts as a collection-like abstraction
for accessing and persisting Aggregates and Entities.
Repositories:
- Hide infrastructure concerns (databases, NoSQL, files, APIs) from the Domain Layer.
- Provide explicit access patterns tailored to Aggregates.
- Enable testability by allowing domain logic to operate without infrastructure coupling.
- Support transactionally consistent operations when needed.
At ConnectSoft, designing strong, tactical Repositories is critical for:
- Microservices resilience
- Event-sourced workflows
- CQRS separation
- AI and SaaS system evolution
Concept Definition¶
A Repository:
| Aspect | Description |
|---|---|
| Purpose | Abstracts Aggregate persistence and retrieval. |
| Ownership | Each Aggregate typically has its own Repository. |
| Access Mode | Operates in terms of Aggregates (not raw tables, DTOs, or documents). |
| Boundary | Lives at the Application Layer boundary — interfaces defined toward Domain Layer, implementations in Infrastructure Layer. |
| Lifecycle Support | Supports add, update, delete, and query operations based on domain needs. |
📚 Why Repositories Matter at ConnectSoft¶
✅ Decouple Domain from Infrastructure
- Domain logic remains clean — it never talks to databases directly.
✅ Enable Persistence Agility
- Databases (SQL, NoSQL, Event Store) can change without modifying domain logic.
✅ Simplify Testing
- Mock Repositories allow pure domain behavior testing without real database dependencies.
✅ Support Consistency Boundaries
- Repository transactions enforce aggregate consistency across modifications.
✅ Encourage Tactical Design
- Each Repository models exactly what the Aggregate needs — no leaky abstractions or generic "save everything" patterns.
Repository vs DAO (Data Access Object)¶
| Aspect | Repository | DAO |
|---|---|---|
| Scope | Aggregate-oriented | Table or record-oriented |
| Design Level | Tactical (business abstraction) | Technical (storage abstraction) |
| Clients | Domain/Use Cases | Infrastructure services |
| Operations | Business-driven (e.g., FindActiveOrder) | CRUD-driven (select, update, delete) |
| Focus | Language of the domain | Language of the database |
🧩 Visual: Repository in System Layers¶
flowchart TD
UI["UI/API Layer"]
AppService["Application Service"]
Repository["Repository (Aggregate Gateway)"]
Database["Database / Storage Layer"]
UI --> AppService
AppService --> Repository
Repository --> Database
✅ Application Services use Repositories to interact with Aggregates.
✅ Domain Layer remains isolated from database or infrastructure concerns.
Strategic Design Principles for Repositories¶
At ConnectSoft, we model Repositories to protect the domain model,
support flexible persistence evolution, and enable testable, tactical architectures.
📚 Core Principles for Repository Design¶
✅ Aggregate-Centric Access
- Repositories expose operations in terms of Aggregates, not database tables or technical schemas.
✅ Keep Interfaces Small and Focused
- Expose only methods actually needed by the domain use cases (e.g.,
GetActiveSubscriptionByUserId()), not general-purpose CRUD.
✅ Isolate Persistence Infrastructure
- Domain and Application Layers never depend on ORM models, SQL, or NoSQL documents.
✅ Support Transaction Consistency for Aggregates
- Changes across one Aggregate must be transactionally consistent — enforced via the Repository + Unit of Work.
✅ Enable Testability
- Repositories should allow in-memory, mock, or fake implementations for easy testing.
✅ Return Rich Domain Models, Not Anemic DTOs
- Always return fully loaded Aggregates or Entities, ready for domain behavior execution.
✅ Support Optimistic Concurrency Where Needed
- For distributed systems, implement versioning to avoid lost updates.
📚 Repository Interface Example (Tactical)¶
public interface IOrderRepository
{
Task<Order> GetByIdAsync(Guid orderId);
Task<IEnumerable<Order>> GetPendingOrdersAsync();
Task AddAsync(Order order);
Task UpdateAsync(Order order);
Task DeleteAsync(Order order);
}
✅ Operates purely in terms of the Order Aggregate.
✅ No leaking of database-specific details.
Transaction Handling in Repositories¶
✅ At ConnectSoft, transactional consistency is critical when modifying Aggregates.
| Context | Guideline |
|---|---|
| Single Aggregate Changes | Repository methods save or update one Aggregate per transaction. |
| Multiple Aggregates | Use Application Services + Domain Services to coordinate, but respect one Repository per Aggregate boundary. |
| Unit of Work Pattern | Aggregate all Repository changes into a single transaction boundary at Application Service level when needed. |
| Outbox Pattern for Events | Persist domain events inside the same transaction if events are raised by Aggregates. |
🛑 Common Anti-Patterns to Avoid with Repositories¶
| Anti-Pattern | Symptom | Why It's Dangerous |
|---|---|---|
| Generic CRUD Repositories | IGenericRepository<TEntity> everywhere. |
Forces leaky abstractions, bloats services with meaningless operations. |
| Repository Does Too Much | Repository manages multiple aggregates, reads, writes, queries, events. | Violates SRP (Single Responsibility Principle), impossible to test properly. |
| Direct Database Exposure | Application Service or Domain talks directly to DbContext/SQL. | Breaks layering, hurts domain isolation, kills testability. |
| Entity Framework First Design | Repository models follow database shape. | Breaks DDD language, domain model purity collapses. |
| No Unit of Work When Needed | Each Repository call runs in isolation. | Causes transaction consistency issues when multiple changes must happen together. |
📚 Good vs Bad Repository Patterns¶
| ✅ Good Repository | 🚫 Bad Repository |
|---|---|
| Aggregate-focused methods | Generic Save/Delete without context |
| Operates in Domain language | Operates in SQL/table language |
| Supports clear transaction boundaries | Each method opens/closes separate transactions |
| Returns Aggregates/Entities | Returns raw persistence models |
🧩 Visual: Repository + Unit of Work Transaction Flow¶
sequenceDiagram
participant AppService as Application Service
participant OrderRepo as Order Repository
participant PaymentRepo as Payment Repository
participant UnitOfWork as Unit of Work
participant Database
AppService->>UnitOfWork: Begin Transaction
AppService->>OrderRepo: Add New Order
AppService->>PaymentRepo: Add Payment Record
UnitOfWork->>Database: Commit Transaction
✅ Multiple Repositories work inside one Unit of Work to guarantee transaction consistency.
ConnectSoft.Extensions.PersistenceModel Repository Infrastructure¶
At ConnectSoft, we use the ConnectSoft.Extensions.PersistenceModel package to provide a consistent, powerful foundation for all repository implementations.
Base Interfaces and Classes:¶
IGenericRepository<TEntity, TIdentity>— Base interface providing standard CRUD operationsGenericRepository<TEntity, TIdentity>— Abstract base class for NHibernate-based repositoriesMongoDbRepository<TEntity, TIdentity>— Base class for MongoDB-based repositoriesIUnitOfWork— Unit of Work pattern for transactional consistencyISpecification<TEntity, TIdentity>— Specification pattern for complex queries
Key Characteristics:¶
- Aggregate-Centric — Repositories operate on aggregate roots (
IAggregateRoot<TIdentity>) - Unit of Work Integration — All operations work within transaction boundaries
- Specification Pattern — Supports fluent query building without bloating repository API
- Async/Await Support — Both synchronous and asynchronous methods available
- Multiple Persistence Models — Supports NHibernate, MongoDB, and more
- Keyed Dependency Injection — Supports multiple persistence models in the same microservice
C# Examples: Real-World Repository Modeling at ConnectSoft¶
Repositories are modeled tactically per Aggregate,
supporting domain workflows, transactional safety, and future evolvability.
All examples below are based on real implementations from the ConnectSoft.Saas.ProductsCatalog microservice.
🛠️ Real-World Example: ProductRepository from ConnectSoft.Saas.ProductsCatalog¶
Step 1: Define the Repository Interface¶
Real Implementation from ConnectSoft.Saas.ProductsCatalog.PersistenceModel:
namespace ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Repositories
{
using System;
using ConnectSoft.Extensions.PersistenceModel.Repositories;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
/// <summary>
/// Mediates between the domain and data mapping layers
/// using a collection-like interface for accessing
/// Products domain objects.
/// </summary>
public interface IProductsRepository : IGenericRepository<IProduct, Guid>
{
}
}
Key Pattern Elements:
✅ Extends IGenericRepository<TEntity, TIdentity> — Inherits standard CRUD operations
✅ Aggregate Root Type — Operates on IProduct which extends IAggregateRoot<Guid>
✅ Minimal Interface — No custom methods needed if standard operations suffice
✅ Domain-Focused — Interface is in PersistenceModel layer, not infrastructure
Standard Operations from IGenericRepository<TEntity, TIdentity>:
GetById(TIdentity id)/GetByIdAsync(TIdentity id)— Retrieve by identifierGetAll(bool cacheable = false)/GetAllAsync(bool cacheable = false)— Retrieve all entitiesInsert(TEntity entity)/InsertAsync(TEntity entity)— Add new entityUpdate(TEntity entity)/UpdateAsync(TEntity entity)— Update existing entityDelete(TEntity entity)/DeleteAsync(TEntity entity)— Delete entityDelete(TIdentity id)/DeleteAsync(TIdentity id)— Delete by identifierQuery(Expression<Func<TEntity, bool>> filter)— Query with LINQ expressionsSpecify<TSpecification>()— Fluent specification builder
Step 2: Implement NHibernate Repository¶
Real Implementation from ConnectSoft.Saas.ProductsCatalog.PersistenceModel.NHibernate:
namespace ConnectSoft.Saas.ProductsCatalog.PersistenceModel.NHibernate.Repositories
{
using System;
using ConnectSoft.Extensions.PersistenceModel;
using ConnectSoft.Extensions.PersistenceModel.Repositories;
using ConnectSoft.Extensions.PersistenceModel.Specifications;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
using ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Repositories;
/// <summary>
/// Generic repository implementation that provides unified access
/// to the Products entities stored in underlying data storage.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ProductsRepository" /> class
/// with a specified dependencies.
/// </remarks>
/// <param name="unitOfWork">Unit of work instance.</param>
/// <param name="specificationLocator">Specification locator instance.</param>
public class ProductsRepository(IUnitOfWork unitOfWork, ISpecificationLocator specificationLocator)
: GenericRepository<IProduct, Guid>(unitOfWork, specificationLocator), IProductsRepository
{
}
}
Key Implementation Patterns:
✅ Inherits from GenericRepository<IProduct, Guid> — Base class handles all CRUD operations
✅ Implements IProductsRepository — Satisfies the repository contract
✅ Unit of Work Dependency — All operations work within transaction boundaries
✅ Specification Locator — Enables specification pattern for complex queries
✅ Minimal Implementation — No code needed if base class provides all required functionality
Step 3: Implement MongoDB Repository (Alternative Persistence)¶
Real Implementation showing MongoDB persistence option:
// Ignore Spelling: Mongo
namespace ConnectSoft.Saas.ProductsCatalog.PersistenceModel.MongoDb.Repositories
{
using System;
using ConnectSoft.Extensions.PersistenceModel;
using ConnectSoft.Extensions.PersistenceModel.MongoDb.Repositories;
using ConnectSoft.Extensions.PersistenceModel.Specifications;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
using ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Repositories;
using MongoDB.Driver;
/// <summary>
/// MongoDb repository implementation that provides unified access
/// to the Products entities stored in underlying data storage.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ProductsMongoDbRepository"/> class.
/// </remarks>
/// <param name="unitOfWork">unit of work instance.</param>
/// <param name="mongoDatabase">the mongoDb database.</param>
/// <param name="specificationLocator">the specification locator.</param>
public class ProductsMongoDbRepository(IUnitOfWork unitOfWork, IMongoDatabase mongoDatabase, ISpecificationLocator specificationLocator)
: MongoDbRepository<IProduct, Guid>(unitOfWork, mongoDatabase, specificationLocator), IProductsRepository
{
}
}
✅ Same Interface, Different Implementation — IProductsRepository can be backed by NHibernate or MongoDB
✅ Infrastructure Agnostic — Application Services don't know which persistence technology is used
✅ Flexible Deployment — Can switch persistence models without changing domain or application code
Step 4: Using Repository in Application Services¶
Real Implementation from ConnectSoft.Saas.ProductsCatalog.DomainModel.Impl:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel.Impl
{
using System;
using System.Threading.Tasks;
using ConnectSoft.Extensions.PersistenceModel;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
using ConnectSoft.Saas.ProductsCatalog.EntityModel.PocoEntities;
using ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Repositories;
/// <summary>
/// Default IProductsProcessor implementation to process, manage and store Products.
/// </summary>
public class DefaultProductsProcessor : IProductsProcessor
{
private readonly IProductsRepository repository;
private readonly IUnitOfWork unitOfWork;
// ... constructor ...
/// <summary>
/// Creates a new Product aggregate root.
/// </summary>
public async Task<IProduct> CreateProduct(CreateProductInput input, CancellationToken token = default)
{
// 1. Check if product already exists
IProduct existingProduct = await this.repository.GetByIdAsync(input.ProductId);
if (existingProduct != null)
{
throw new ProductAlreadyExistsException(input.ProductId);
}
// 2. Create new aggregate root
ProductEntity newProduct = new ProductEntity()
{
ProductId = input.ProductId,
CreationDate = this.dateTimeProvider.GetUtcNow().DateTime,
Name = "Salesforce CRM Cloud",
Status = ProductStatusEnumeration.Active,
// ... other properties ...
};
// 3. Persist within transaction
this.unitOfWork.ExecuteTransactional(() =>
{
this.repository.Insert(newProduct); // ✅ Using repository
});
return newProduct;
}
/// <summary>
/// Deletes a Product aggregate root.
/// </summary>
public async Task DeleteProduct(DeleteProductInput input, CancellationToken token = default)
{
// 1. Load aggregate root
IProduct productToDelete = await this.repository.GetByIdAsync(input.ProductId);
if (productToDelete == null)
{
throw new ProductNotFoundException(input.ProductId);
}
// 2. Delete within transaction
this.unitOfWork.ExecuteTransactional(() =>
{
this.repository.Delete(productToDelete); // ✅ Using repository
});
}
}
}
Key Application Service Patterns:
✅ Repository Injection — Repository is injected via constructor
✅ Unit of Work Integration — ExecuteTransactional() ensures atomic operations
✅ Async/Await — All repository operations use async methods
✅ Null Handling — Repository methods return null if entity not found
✅ Transaction Boundaries — All persistence operations wrapped in transactions
Step 5: Using Specification Pattern for Complex Queries¶
Real Implementation showing how to use specifications:
// Ignore Spelling: Queryable
namespace ConnectSoft.Saas.ProductsCatalog.PersistenceModel.NHibernate.Specifications
{
using System;
using System.Linq;
using ConnectSoft.Extensions.PersistenceModel;
using ConnectSoft.Extensions.PersistenceModel.Specifications.Queryable;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
using ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Specifications;
/// <summary>
/// IQueryable based specification to search Products entities.
/// </summary>
public class ProductsQueryableSpecification(IUnitOfWorkConvertor unitOfWorkConvertor)
: QueryableSpecification<IProduct, Guid>(unitOfWorkConvertor), IProductsSpecification
{
// Custom query methods can be added here
// Example: FindActiveProducts(), FindByCategory(string category), etc.
}
}
Usage in Application Service:
// Using Specification pattern for complex queries
var specification = repository.Specify<ProductsQueryableSpecification>();
var activeProducts = specification
.Where(p => p.Status == ProductStatusEnumeration.Active)
.ToList();
// Or using Query method with LINQ expressions
var freemiumProducts = repository.Query(p =>
p.Status == ProductStatusEnumeration.Active &&
p.PricingModel.PricingType == PricingTypeEnumeration.Freemium
);
✅ Fluent Query Building — Specifications enable composable queries
✅ Type Safety — Compile-time checking of query expressions
✅ Separation of Concerns — Complex queries isolated in specification classes
📚 Key Lessons from ConnectSoft Repository Examples¶
✅ Repositories inherit from IGenericRepository<TEntity, TIdentity> and implement aggregate-specific interfaces
✅ Implementation classes inherit from GenericRepository<TEntity, TIdentity> (NHibernate) or MongoDbRepository<TEntity, TIdentity> (MongoDB)
✅ Unit of Work pattern ensures transactional consistency
✅ Specification pattern enables complex queries without bloating repository API
✅ Multiple persistence models (NHibernate, MongoDB) can implement the same repository interface
✅ Repository interfaces are in PersistenceModel layer, implementations in infrastructure-specific projects
✅ Application Services use repositories through interfaces, remaining infrastructure-agnostic
📋 Complete Implementation Template¶
Here's a complete template you can use to create new repositories following ConnectSoft patterns:
namespace YourNamespace.PersistenceModel.Repositories
{
using System;
using ConnectSoft.Extensions.PersistenceModel.Repositories;
using YourNamespace.EntityModel;
/// <summary>
/// Mediates between the domain and data mapping layers
/// using a collection-like interface for accessing
/// YourAggregateRoot domain objects.
/// </summary>
public interface IYourAggregateRootRepository : IGenericRepository<IYourAggregateRoot, Guid>
{
// Add custom methods here if needed (e.g., FindActiveItems(), FindByCategory())
}
}
namespace YourNamespace.PersistenceModel.NHibernate.Repositories
{
using System;
using ConnectSoft.Extensions.PersistenceModel;
using ConnectSoft.Extensions.PersistenceModel.Repositories;
using ConnectSoft.Extensions.PersistenceModel.Specifications;
using YourNamespace.EntityModel;
using YourNamespace.PersistenceModel.Repositories;
/// <summary>
/// Generic repository implementation that provides unified access
/// to the YourAggregateRoot entities stored in underlying data storage.
/// </summary>
/// <param name="unitOfWork">Unit of work instance.</param>
/// <param name="specificationLocator">Specification locator instance.</param>
public class YourAggregateRootRepository(IUnitOfWork unitOfWork, ISpecificationLocator specificationLocator)
: GenericRepository<IYourAggregateRoot, Guid>(unitOfWork, specificationLocator), IYourAggregateRootRepository
{
// Override base methods or add custom methods here if needed
}
}
✅ Follow this pattern for all new repositories to ensure consistency across ConnectSoft projects.
🛠️ Example 2: Finance — AccountRepository (Soft Deletion Handling)¶
public interface IAccountRepository
{
Task<Account> GetByIdAsync(Guid accountId);
Task<IEnumerable<Account>> GetActiveAccountsAsync();
Task AddAsync(Account account);
Task DeactivateAsync(Account account);
}
public class AccountRepository : IAccountRepository
{
private readonly DbContext _context;
public AccountRepository(DbContext context)
{
_context = context;
}
public async Task<Account> GetByIdAsync(Guid accountId)
{
return await _context.Set<Account>()
.Where(a => a.IsActive)
.SingleOrDefaultAsync(a => a.Id == accountId);
}
public async Task<IEnumerable<Account>> GetActiveAccountsAsync()
{
return await _context.Set<Account>()
.Where(a => a.IsActive)
.ToListAsync();
}
public async Task AddAsync(Account account)
{
await _context.Set<Account>().AddAsync(account);
}
public async Task DeactivateAsync(Account account)
{
account.Deactivate();
_context.Set<Account>().Update(account);
}
}
✅ Handles soft deletion (deactivation) properly.
✅ All queries automatically filter for active accounts.
🎯 ConnectSoft Repository Checklist¶
When implementing repositories at ConnectSoft, ensure:
| Checklist Item | Description | Example |
|---|---|---|
✅ Extend IGenericRepository<TEntity, TIdentity> |
Use base interface for standard operations | IGenericRepository<IProduct, Guid> |
| ✅ Create Aggregate-Specific Interface | Define repository interface in PersistenceModel layer | IProductsRepository : IGenericRepository<IProduct, Guid> |
| ✅ Inherit from Base Repository Class | Use GenericRepository (NHibernate) or MongoDbRepository (MongoDB) |
GenericRepository<IProduct, Guid> |
| ✅ Inject Unit of Work | All operations require IUnitOfWork for transactions |
Constructor: IUnitOfWork unitOfWork |
| ✅ Inject Specification Locator | Enable specification pattern for complex queries | Constructor: ISpecificationLocator specificationLocator |
| ✅ Use Async Methods | Prefer async/await for all I/O operations | GetByIdAsync(), InsertAsync() |
| ✅ Wrap in Transactions | Use UnitOfWork.ExecuteTransactional() for atomic operations |
unitOfWork.ExecuteTransactional(() => { ... }) |
| ✅ Handle Null Returns | Repository methods return null if entity not found |
if (entity == null) throw new NotFoundException() |
| ✅ Support Specifications | Use specification pattern for complex queries | repository.Specify<TSpecification>() |
| ✅ Multiple Persistence Models | Support NHibernate, MongoDB, or both in same microservice | Use keyed dependency injection for multiple models |
🛠️ Example 3: Healthcare — PatientRepository with Specification Pattern¶
public interface IPatientRepository
{
Task<Patient> GetByIdAsync(Guid patientId);
Task<IEnumerable<Patient>> FindAsync(ISpecification<Patient> specification);
Task AddAsync(Patient patient);
}
public interface ISpecification<T>
{
Expression<Func<T, bool>> ToExpression();
}
public class ActivePatientsSpecification : ISpecification<Patient>
{
public Expression<Func<Patient, bool>> ToExpression()
{
return patient => patient.IsActive && !patient.IsArchived;
}
}
public class PatientRepository : IPatientRepository
{
private readonly DbContext _context;
public PatientRepository(DbContext context)
{
_context = context;
}
public async Task<Patient> GetByIdAsync(Guid patientId)
{
return await _context.Set<Patient>().FindAsync(patientId);
}
public async Task<IEnumerable<Patient>> FindAsync(ISpecification<Patient> specification)
{
return await _context.Set<Patient>()
.Where(specification.ToExpression())
.ToListAsync();
}
public async Task AddAsync(Patient patient)
{
await _context.Set<Patient>().AddAsync(patient);
}
}
✅ Supports flexible dynamic queries without bloating Repository API.
✅ Keeps Repository clean and extensible over time.
📚 Lessons from Advanced Repository Modeling¶
| Good Practice | Why It Matters |
|---|---|
| Aggregate-centered methods | Keeps use cases simple and tactical. |
| Support for dynamic queries (Specification) | Avoid endless method expansion. |
| Handle Soft Deletes internally | Protects external callers from data inconsistencies. |
| Infrastructure isolation | Domain models never leak infrastructure concerns. |
| Compose clean Unit of Work transactions | Enables distributed consistency across Aggregates. |
🧩 Visual: Repository Usage in an Application Service¶
sequenceDiagram
participant AppService as Application Service
participant OrderRepository
participant OrderAggregate
AppService->>OrderRepository: GetByIdAsync(orderId)
OrderRepository-->>AppService: Order Aggregate
AppService->>OrderAggregate: Execute Business Behavior (Place Order)
AppService->>OrderRepository: UpdateAsync(Order Aggregate)
✅ Application Services use Repositories tactically to orchestrate domain behavior safely.
Best Practices for Repositories¶
At ConnectSoft, Repositories are treated as critical tactical patterns —
preserving domain purity, enabling evolution, and safeguarding transactional resilience across microservices and complex systems.
📚 Best Practices Checklist¶
✅ Model Repositories Per Aggregate
- Each Aggregate gets its own Repository abstraction.
✅ Expose Aggregate-Specific Operations Only
- No generic CRUD methods leaking everywhere.
✅ Operate at the Domain Level
- Expose methods and queries meaningful to the business, not the database.
✅ Protect Transaction Consistency
- Changes within a transaction stay isolated until committed via Unit of Work.
✅ Enable In-Memory Testing Easily
- Repositories should allow mocking/faking for clean, fast unit testing.
✅ Support Dynamic Queries via Specifications
- Avoid endless method expansions with Specification Pattern or Query Objects.
✅ Handle Soft Deletes or Status Filtering Internally
- Never expose inactive/archived entities unintentionally.
✅ Infrastructure Stays Hidden
- No exposure of database, NoSQL, or ORM concerns to Application or Domain Layers.
✅ Design for Resilience
- Apply patterns like retrying transient failures and outbox integration where necessary.
Conclusion¶
Repositories at ConnectSoft are not just technical patterns —
they are strategic design artifacts that enable:
- Domain isolation and protection
- Evolution of storage technologies
- Clear tactical orchestration inside Application Services
- Scalable, resilient event-driven microservice workflows
When Repositories are poorly modeled:
- Domain models rot into persistence-driven shapes.
- Testing becomes fragile and slow.
- Transactional consistency becomes unreliable.
- Evolution toward event-driven or distributed systems becomes risky.
When modeled properly:
- Repositories shield the domain.
- Systems adapt faster to business change.
- Architectural integrity is preserved for the long term.
At ConnectSoft, Repositories stand guard over the domain,
ensuring that every Aggregate can grow, evolve, and thrive —
regardless of how the technology landscape shifts beneath it.
"Repositories are the gates;
Guard them well, and your domain will flourish."
References¶
-
Books and Literature
- Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software
- Vaughn Vernon — Implementing Domain-Driven Design
- Jimmy Nilsson — Applying Domain-Driven Design and Patterns
-
Online Resources
-
ConnectSoft Packages and Code
ConnectSoft.Extensions.PersistenceModel— Repository infrastructure:IGenericRepository<TEntity, TIdentity>— Base repository interface with standard CRUD operationsGenericRepository<TEntity, TIdentity>— Abstract base class for NHibernate repositoriesIUnitOfWork— Unit of Work pattern for transactional consistencyISpecification<TEntity, TIdentity>— Specification pattern for complex queries- Async and synchronous method support
ConnectSoft.Extensions.PersistenceModel.NHibernate— NHibernate-specific implementations:GenericRepository<TEntity, TIdentity>— NHibernate repository base classQueryableSpecification<TEntity, TIdentity>— LINQ-based specification support
ConnectSoft.Extensions.PersistenceModel.MongoDb— MongoDB-specific implementations:MongoDbRepository<TEntity, TIdentity>— MongoDB repository base class- MongoDB-specific query and specification support
- Real-World Examples — See
ConnectSoft.Saas.ProductsCatalog.PersistenceModelfor production implementations:IProductsRepository/ProductsRepository— Complete repository exampleProductsMongoDbRepository— MongoDB repository exampleProductsQueryableSpecification— Specification pattern exampleDefaultProductsProcessor— Application Service using repository
-
ConnectSoft Internal Standards
- ConnectSoft Repository and Persistence Layer Guidelines
- ConnectSoft Microservices Transaction Patterns
- ConnectSoft Event-Driven Outbox Pattern Blueprints