Domain Services¶
Info
At ConnectSoft, Domain Services are used with precision:
only when a behavior does not naturally belong to an Entity or Value Object,
but is still pure business logic critical to the domain.
Introduction¶
In Domain-Driven Design (DDD), a Domain Service encapsulates business operations
that do not belong naturally to any single Entity or Value Object,
but still represent important domain behaviors.
A Domain Service:
- Is stateless — it operates using domain objects but doesn't hold state itself.
- Coordinates multiple aggregates or entities to fulfill complex domain-specific actions.
- Belongs purely to the domain layer — no infrastructure or application orchestration concerns allowed.
At ConnectSoft, Domain Services:
- Model complex business rules cleanly without bloating Aggregates.
- Maintain domain purity while allowing complex multi-entity collaborations.
- Serve as a critical tactical modeling tool when needed — but never by default.
Concept Definition¶
A Domain Service:
| Aspect | Description |
|---|---|
| Purpose | Encapsulate domain behavior that doesn't fit inside a single entity or value object. |
| Nature | Stateless, reusable, pure business logic. |
| Ownership | Remains fully inside the domain layer — no infrastructure dependencies. |
| Input/Output | Operates over domain types (Aggregates, Entities, Value Objects). |
| Examples | Transferring funds between accounts, calculating delivery costs, applying discounts across orders. |
📚 Why Domain Services Matter at ConnectSoft¶
✅ Preserve Aggregate Simplicity
- Aggregates stay focused on their core responsibilities.
✅ Coordinate Cross-Aggregate Logic
- Complex interactions happen safely without coupling models improperly.
✅ Maintain Pure Business Logic
- Domain Services model real-world business activities, free of technical noise.
✅ Keep Application Services Thin
- Application Services orchestrate Domain Services — they don't duplicate domain logic.
✅ Enable High Reusability
- Domain Services can be reused across use cases when domain rules overlap.
When to Use a Domain Service¶
✅ Behavior involves multiple Aggregates or Entities.
✅ Behavior is pure domain logic, not technical concerns.
✅ Behavior does not naturally belong to any single Entity or Value Object.
✅ Business experts would recognize the operation as part of their language.
🧩 Visual: Where Domain Services Sit in Clean Architecture¶
flowchart TD
UserInterface["UI Layer (API / Controller)"]
ApplicationService["Application Service (Use Case Orchestrator)"]
DomainService["Domain Service (Pure Business Behavior)"]
DomainModel["Domain Entities and Aggregates"]
UserInterface --> ApplicationService
ApplicationService --> DomainService
DomainService --> DomainModel
✅ Domain Services do not replace Aggregates.
✅ They support Aggregates by coordinating complex behaviors cleanly.
Strategic Design Principles for Domain Services¶
Domain Services must be modeled intentionally —
only when behavior cannot fit naturally inside an Entity or Value Object,
but still belongs to the domain core, not to the infrastructure or application layers.
At ConnectSoft, we model Domain Services carefully and tactically.
📚 Core Principles for Good Domain Services¶
✅ Model Pure Business Behavior
- The service encapsulates complex domain logic — not technical operations.
✅ Statelessness
- Domain Services should hold no internal state between calls.
- All state lives inside domain objects (Aggregates, Entities, Value Objects).
✅ Use Domain Types in Inputs and Outputs
- Operate on domain types — no DTOs, persistence models, or infrastructure dependencies.
✅ Small, Focused Responsibility
- Each Domain Service models one cohesive business concept or operation.
✅ Recognizable by Business Experts
- Business people should understand and recognize the service as a real operation (e.g., "Transfer Funds", "Calculate Delivery Cost").
✅ Infrastructure-Free
- Domain Services should not access databases, messaging systems, APIs, or external technical concerns directly.
✅ Testable Independently
- You should be able to test a Domain Service easily using pure domain objects.
🎯 Good vs Bad Domain Services¶
| ✅ Good Domain Service | 🚫 Bad Domain Service |
|---|---|
| Models pure domain behavior | Coordinates technical concerns (DBs, APIs) |
| Stateless | Holds session or transactional state |
| Uses Entities and Value Objects | Uses DTOs or database models |
| Operates inside Domain Layer | Leaks into Infrastructure or Application layers |
| Small, focused operations | Bloated with multiple unrelated responsibilities |
| Testable without infrastructure | Requires database or external system to test |
🛑 Common Anti-Patterns to Avoid¶
| Anti-Pattern | Symptom | Why It's Dangerous |
|---|---|---|
| Service-Anemic Domain | Everything becomes a "service" because entities lack behavior. | Procedural code explosion. Domain loses expressiveness. |
| Infrastructure-Laden Services | Domain Services directly query databases, send emails, call APIs. | Breaks clean layering, fragile to change. |
| Bloated Domain Services | Services grow to hundreds of lines coordinating everything. | Hard to test, hard to evolve, violates SRP (Single Responsibility Principle). |
| Mixed Responsibility Services | One service handles both domain behavior and application orchestration. | Confusing separation, breaks clean architecture. |
📚 How to Recognize a Need for a Domain Service¶
✅ Complex domain operation not naturally owned by any single Entity.
✅ Business experts talk about it as a standalone operation (not tied to a single concept).
✅ Operation requires coordination between multiple domain concepts.
✅ Behavior is pure domain logic — no infrastructure concerns.
🧩 Visual: Good Domain Service Interaction Flow¶
sequenceDiagram
participant ApplicationService as Application Service
participant DomainService as Domain Service
participant Aggregate1 as Aggregate A
participant Aggregate2 as Aggregate B
ApplicationService->>DomainService: Orchestrate Business Operation
DomainService->>Aggregate1: Perform Behavior 1
DomainService->>Aggregate2: Perform Behavior 2
DomainService-->>ApplicationService: Return Result
✅ Clear delegation.
✅ Pure domain coordination.
✅ No technical contamination inside Domain Services.
ConnectSoft.Extensions.DomainModel.IDomainService Interface¶
At ConnectSoft, we use the ConnectSoft.Extensions.DomainModel.IDomainService interface
to mark domain services and enable dependency injection and testing patterns.
Base Interface:¶
IDomainService— Marker interface for domain services (empty interface used for identification)
Key Characteristics:¶
- Marker Interface — Empty interface used to identify domain services for dependency injection
- Dependency Injection — Services implementing
IDomainServicecan be auto-registered via reflection - Testability — Interfaces enable easy mocking and testing
- Separation of Concerns — Clear contract for domain-level operations
Note: At ConnectSoft, Domain Services that extend IDomainService may use repositories and UnitOfWork through interfaces. This pattern allows domain services to orchestrate aggregate operations while maintaining clean architecture through dependency injection.
C# Examples: Real-World Domain Services at ConnectSoft¶
Domain Services orchestrate domain objects to fulfill meaningful, real-world business operations —
while keeping Aggregates simple, clean, and focused.
All examples below are based on real implementations from the ConnectSoft.Saas.ProductsCatalog microservice.
🛠️ Real-World Example: Product Domain Services from ConnectSoft.Saas.ProductsCatalog¶
Step 1: Define Domain Service Interface¶
Real Implementation from ConnectSoft.Saas.ProductsCatalog.DomainModel:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel
{
using System.Threading;
using System.Threading.Tasks;
using ConnectSoft.Extensions.DomainModel;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
/// <summary>
/// This interface provides methods to process, manage and store Products.
/// </summary>
public interface IProductsProcessor : IDomainService
{
/// <summary>
/// Process, create and store a Products.
/// </summary>
/// <param name="input">The create Product's input details.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>Returns a created Product details.</returns>
Task<IProduct> CreateProduct(CreateProductInput input, CancellationToken token = default);
/// <summary>
/// Delete a given Product.
/// </summary>
/// <param name="input">The create Product's input details.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task DeleteProduct(DeleteProductInput input, CancellationToken token = default);
}
}
Key Pattern Elements:
✅ Extends IDomainService — Marker interface for domain service identification
✅ Domain-Focused Methods — Operations on aggregate roots (IProduct)
✅ Input Types — Uses dedicated input classes (CreateProductInput, DeleteProductInput)
✅ Async/Await — All operations are asynchronous
✅ CancellationToken Support — Proper async cancellation token handling
Step 2: Define Input Types¶
Real Implementation showing input type pattern:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel
{
using System;
/// <summary>
/// Create Product details input -
/// transient entity used in business layer components as input argument(s).
/// </summary>
public class CreateProductInput
{
/// <summary>
/// Gets or sets an object identifier.
/// </summary>
public Guid ProductId { get; set; }
}
}
✅ Simple Input Classes — Dedicated input types for each operation
✅ Transient Objects — Used only for passing data to domain services
✅ Clear Naming — CreateProductInput, DeleteProductInput, etc.
Step 3: Implement Domain Service¶
Real Implementation from ConnectSoft.Saas.ProductsCatalog.DomainModel.Impl:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel.Impl
{
using System;
using System.Threading;
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;
using ConnectSoft.Extensions.MessagingModel;
using ConnectSoft.Saas.ProductsCatalog.MessagingModel;
using FluentValidation;
using Microsoft.Extensions.Logging;
/// <summary>
/// Default IProductsProcessor implementation to process, manage and store Products.
/// </summary>
public class DefaultProductsProcessor : IProductsProcessor
{
private readonly ILogger<DefaultProductsProcessor> logger;
private readonly TimeProvider dateTimeProvider;
private readonly IProductsRepository repository;
private readonly IUnitOfWork unitOfWork;
private readonly IValidator<CreateProductInput> createProductInputValidator;
private readonly IValidator<DeleteProductInput> deleteProductInputValidator;
private readonly IEventBus eventBus;
private readonly MicroserviceTemplateMetrics meters;
// ... constructor ...
/// <inheritdoc/>
public async Task<IProduct> CreateProduct(CreateProductInput input, CancellationToken token = default)
{
// 1. Validate input
await this.createProductInputValidator.ValidateAndThrowAsync(input, token);
// 2. Check if product already exists
IProduct existingProduct = await this.repository.GetByIdAsync(input.ProductId);
if (existingProduct != null)
{
throw new ProductAlreadyExistsException(input.ProductId);
}
// 3. Create new aggregate root
ProductEntity newProduct = new ProductEntity()
{
ProductId = input.ProductId,
CreationDate = this.dateTimeProvider.GetUtcNow().DateTime,
Name = "Salesforce CRM Cloud",
DisplayName = "Salesforce CRM Cloud",
Key = "Salesforce-CRM-Cloud",
Description = "Unified CRM platform for sales, marketing, and customer support.",
ProductCategory = "CRM",
Status = ProductStatusEnumeration.Active,
LatestVersion = "v1.0.0",
ReleaseNotes = "Initial release.",
};
// 4. Persist within transaction
this.unitOfWork.ExecuteTransactional(() =>
{
this.repository.Insert(newProduct);
});
// 5. Publish domain event
await this.eventBus.PublishEvent<ProductCreatedEvent>(
new ProductCreatedEvent()
{
ProductId = newProduct.ProductId,
},
token);
// 6. Update metrics
this.meters.AddProduct();
this.meters.IncreaseTotalProducts();
return newProduct;
}
/// <inheritdoc/>
public async Task DeleteProduct(DeleteProductInput input, CancellationToken token = default)
{
// 1. Validate input
await this.deleteProductInputValidator.ValidateAndThrowAsync(input, token);
// 2. Load aggregate root
IProduct productToDelete = await this.repository.GetByIdAsync(input.ProductId);
if (productToDelete == null)
{
throw new ProductNotFoundException(input.ProductId);
}
// 3. Delete within transaction
this.unitOfWork.ExecuteTransactional(() =>
{
this.repository.Delete(productToDelete);
});
// 4. Update metrics
this.meters.DeleteProduct();
this.meters.DecreaseTotalProducts();
}
}
}
Key Implementation Patterns:
✅ Implements IProductsProcessor — Satisfies the domain service interface
✅ Repository Pattern — Uses IProductsRepository to load and persist aggregates
✅ Unit of Work — ExecuteTransactional() ensures atomic operations
✅ Domain Events — Publishes events after successful persistence
✅ Validation — Uses FluentValidation for input validation
✅ Logging — Structured logging with scoped context
✅ Metrics — Updates application metrics
✅ Async/Await — All operations are asynchronous
Step 4: Query Domain Service Example¶
Real Implementation showing read-only domain service:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel
{
using System.Threading;
using System.Threading.Tasks;
using ConnectSoft.Extensions.DomainModel;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
/// <summary>
/// This interface provides operations to retrieve Products.
/// </summary>
public interface IProductsRetriever : IDomainService
{
/// <summary>
/// Gets Product details.
/// </summary>
/// <param name="input">The input details.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>Returns a found Product.</returns>
Task<IProduct> GetProductDetails(GetProductDetailsInput input, CancellationToken token = default);
}
}
Implementation:
namespace ConnectSoft.Saas.ProductsCatalog.DomainModel.Impl
{
using System;
using System.Threading;
using System.Threading.Tasks;
using ConnectSoft.Saas.ProductsCatalog.EntityModel;
using ConnectSoft.Saas.ProductsCatalog.PersistenceModel.Repositories;
using FluentValidation;
using Microsoft.Extensions.Logging;
/// <summary>
/// Default implementation to retrieve Products.
/// </summary>
public class DefaultProductsRetriever : IProductsRetriever
{
private readonly ILogger<DefaultProductsRetriever> logger;
private readonly IProductsRepository repository;
private readonly IValidator<GetProductDetailsInput> getProductDetailsInputValidator;
private readonly MicroserviceTemplateMetrics meters;
// ... constructor ...
/// <inheritdoc/>
public async Task<IProduct> GetProductDetails(GetProductDetailsInput input, CancellationToken token = default)
{
// 1. Validate input
await this.getProductDetailsInputValidator.ValidateAndThrowAsync(input, token);
// 2. Load aggregate root
IProduct requestedEntity = await this.repository.GetByIdAsync(input.ProductId);
if (requestedEntity == null)
{
throw new ProductNotFoundException(input.ProductId);
}
// 3. Update metrics
this.meters.ReadProduct();
return requestedEntity;
}
}
}
✅ Read-Only Operations — Query operations don't modify aggregates
✅ Same Patterns — Validation, logging, metrics, error handling
✅ Separation of Concerns — Separate interfaces for commands (IProductsProcessor) and queries (IProductsRetriever)
📚 Key Lessons from ConnectSoft Domain Service Examples¶
✅ Domain Service interfaces extend IDomainService for identification and dependency injection
✅ Input types are dedicated classes (e.g., CreateProductInput) for each operation
✅ Domain Services use repositories and UnitOfWork through interfaces for persistence
✅ Domain Services publish events via IEventBus after successful operations
✅ FluentValidation is used for input validation before domain operations
✅ Structured logging with scoped context provides traceability
✅ Metrics are updated to track domain operations
✅ Exceptions are domain-specific (e.g., ProductNotFoundException)
✅ Operations are asynchronous with proper CancellationToken support
📋 Complete Implementation Template¶
Here's a complete template you can use to create new domain services following ConnectSoft patterns:
namespace YourNamespace.DomainModel
{
using System.Threading;
using System.Threading.Tasks;
using ConnectSoft.Extensions.DomainModel;
using YourNamespace.EntityModel;
/// <summary>
/// This interface provides methods to process [Your Aggregate].
/// </summary>
public interface IYourAggregateProcessor : IDomainService
{
/// <summary>
/// Process, create and store a [Your Aggregate].
/// </summary>
/// <param name="input">The create [Your Aggregate]'s input details.</param>
/// <param name="token">Cancellation token.</param>
/// <returns>Returns a created [Your Aggregate] details.</returns>
Task<IYourAggregate> CreateYourAggregate(CreateYourAggregateInput input, CancellationToken token = default);
}
}
namespace YourNamespace.DomainModel
{
using System;
/// <summary>
/// Create [Your Aggregate] details input -
/// transient entity used in business layer components as input argument(s).
/// </summary>
public class CreateYourAggregateInput
{
/// <summary>
/// Gets or sets an object identifier.
/// </summary>
public Guid YourAggregateId { get; set; }
}
}
namespace YourNamespace.DomainModel.Impl
{
using System.Threading;
using System.Threading.Tasks;
using ConnectSoft.Extensions.PersistenceModel;
using YourNamespace.EntityModel;
using YourNamespace.PersistenceModel.Repositories;
using FluentValidation;
using Microsoft.Extensions.Logging;
/// <summary>
/// Default IYourAggregateProcessor implementation.
/// </summary>
public class DefaultYourAggregateProcessor : IYourAggregateProcessor
{
private readonly ILogger<DefaultYourAggregateProcessor> logger;
private readonly IYourAggregateRepository repository;
private readonly IUnitOfWork unitOfWork;
private readonly IValidator<CreateYourAggregateInput> createInputValidator;
// ... constructor ...
/// <inheritdoc/>
public async Task<IYourAggregate> CreateYourAggregate(CreateYourAggregateInput input, CancellationToken token = default)
{
// 1. Validate input
await this.createInputValidator.ValidateAndThrowAsync(input, token);
// 2. Check if already exists
IYourAggregate existing = await this.repository.GetByIdAsync(input.YourAggregateId);
if (existing != null)
{
throw new YourAggregateAlreadyExistsException(input.YourAggregateId);
}
// 3. Create new aggregate
YourAggregateEntity newEntity = new YourAggregateEntity()
{
YourAggregateId = input.YourAggregateId,
// ... other properties ...
};
// 4. Persist within transaction
this.unitOfWork.ExecuteTransactional(() =>
{
this.repository.Insert(newEntity);
});
return newEntity;
}
}
}
✅ Follow this pattern for all new domain services to ensure consistency across ConnectSoft projects.
🛠️ Example 2: E-Commerce — CalculateDiscountService¶
// Domain Service: Calculate Discount for an Order
public class CalculateDiscountService
{
private readonly IDiscountPolicy _discountPolicy;
public CalculateDiscountService(IDiscountPolicy discountPolicy)
{
_discountPolicy = discountPolicy ?? throw new ArgumentNullException(nameof(discountPolicy));
}
public decimal CalculateDiscount(Order order)
{
if (order == null) throw new ArgumentNullException(nameof(order));
return _discountPolicy.ApplyDiscount(order.TotalAmount);
}
}
// Example Policy Interface
public interface IDiscountPolicy
{
decimal ApplyDiscount(decimal totalAmount);
}
// Example Policy Implementation
public class LoyaltyDiscountPolicy : IDiscountPolicy
{
public decimal ApplyDiscount(decimal totalAmount)
{
return totalAmount >= 100 ? totalAmount * 0.1m : 0;
}
}
✅ Domain Service delegates pure discount calculation based on the business policy.
✅ Strategy Pattern applied to swap different discount rules easily.
🛠️ Example 3: Healthcare — ScheduleAppointmentService¶
// Domain Service: Schedule Appointment for Patient
public class ScheduleAppointmentService
{
public Appointment Schedule(Patient patient, DateTime appointmentDate, Provider provider)
{
if (patient == null) throw new ArgumentNullException(nameof(patient));
if (provider == null) throw new ArgumentNullException(nameof(provider));
if (appointmentDate < DateTime.UtcNow)
throw new InvalidOperationException("Cannot schedule an appointment in the past.");
if (!provider.IsAvailable(appointmentDate))
throw new InvalidOperationException("Provider is not available at the selected time.");
var appointment = new Appointment(appointmentDate, provider.Id, patient.Id);
patient.AddAppointment(appointment);
return appointment;
}
}
✅ Coordination between Patient, Appointment, and Provider entities.
✅ Domain rules enforced (provider availability, future date validation).
🎯 ConnectSoft Domain Service Checklist¶
When implementing domain services at ConnectSoft, ensure:
| Checklist Item | Description | Example |
|---|---|---|
✅ Extend IDomainService |
Use marker interface for identification | IProductsProcessor : IDomainService |
| ✅ Define Interface Contract | Create interface in DomainModel layer | IProductsProcessor with domain methods |
| ✅ Use Dedicated Input Types | Create input classes for each operation | CreateProductInput, DeleteProductInput |
| ✅ Inject Repository via Interface | Use repository interface for persistence | IProductsRepository repository |
| ✅ Inject Unit of Work | Ensure transactional consistency | IUnitOfWork unitOfWork |
| ✅ Use FluentValidation | Validate inputs before operations | IValidator<CreateProductInput> |
| ✅ Publish Domain Events | Signal important domain changes | IEventBus.PublishEvent<T>() |
| ✅ Use Structured Logging | Log operations with scoped context | logger.BeginScope() |
| ✅ Update Metrics | Track domain operations | meters.AddProduct() |
| ✅ Throw Domain Exceptions | Use domain-specific exceptions | ProductNotFoundException |
| ✅ Support CancellationToken | Enable async cancellation | CancellationToken token = default |
| ✅ Separate Commands and Queries | Different interfaces for reads/writes | IProductsProcessor vs IProductsRetriever |
📚 Key Modeling Lessons from ConnectSoft Examples¶
| Good Practice | Why It Matters | ConnectSoft Implementation |
|---|---|---|
Extend IDomainService |
Enables dependency injection and testing | All domain service interfaces extend IDomainService |
| Use Dedicated Input Types | Type-safe operation parameters | CreateProductInput, DeleteProductInput classes |
| Repository Pattern | Abstraction over persistence | IProductsRepository injected via interface |
| Unit of Work Pattern | Transactional consistency | IUnitOfWork.ExecuteTransactional() |
| Domain Events | Loose coupling between bounded contexts | IEventBus.PublishEvent<T>() |
| Input Validation | Ensure correctness before operations | FluentValidation with ValidateAndThrowAsync() |
| Structured Logging | Traceability and debugging | logger.BeginScope() with operation context |
| Metrics Tracking | Observability and monitoring | Update metrics after operations |
| Domain Exceptions | Clear error semantics | ProductNotFoundException, ProductAlreadyExistsException |
| Async/Await | Non-blocking I/O operations | All methods return Task or Task<T> |
| Separate Commands and Queries | CQRS principles | IProductsProcessor (commands) vs IProductsRetriever (queries) |
🧩 Visual: Example — Schedule Appointment Domain Service Flow¶
sequenceDiagram
participant Patient
participant ScheduleAppointmentService
participant Provider
Patient->>ScheduleAppointmentService: Request Appointment
ScheduleAppointmentService->>Provider: Check Availability
ScheduleAppointmentService-->>Patient: Add Appointment to Record
✅ ScheduleAppointmentService orchestrates,
✅ Validates business rules,
✅ Delegates domain changes to Entities and Aggregates.
Best Practices for Domain Services¶
At ConnectSoft, Domain Services are modeled carefully and tactically —
ensuring that domain behavior remains pure, cohesive, and scalable.
📚 Best Practices Checklist¶
✅ Model Pure Business Behavior
- Avoid technical, infrastructural concerns inside Domain Services.
✅ Keep Domain Services Stateless
- No conversational or session data inside services.
✅ Use Domain Models Exclusively
- Operate over Aggregates, Entities, and Value Objects — not DTOs or external models.
✅ Limit Responsibility Scope
- One Domain Service = One Clear Business Responsibility.
✅ Enforce Domain Validations Inside Services
- Validate invariants before mutating domain objects.
✅ No Infrastructure Dependencies
- No database queries, API calls, messaging operations inside Domain Services.
✅ Reusability Where Logical
- Domain Services should be reusable across different Application Services or workflows.
✅ Keep Testability Simple
- Domain Services should be unit tested using domain objects only — no mocks for infrastructure required.
Conclusion¶
Domain Services are a precision tool inside the ConnectSoft architectural arsenal.
When used intentionally:
- They enhance domain expressiveness by modeling real-world business behaviors.
- They protect Aggregates from bloating with unrelated logic.
- They enable scalable, reusable domain operations across use cases.
- They maintain clean architectural layering, separating pure business operations from application and infrastructure concerns.
Without careful Domain Service modeling:
- Systems collapse back into procedural orchestration messes.
- Aggregates get polluted, losing focus and cohesion.
- Testing becomes slow, brittle, and hard to reason about.
At ConnectSoft, we respect, model, and protect Domain Services
to keep our domain models rich, meaningful, and evolution-friendly —
across SaaS, AI, finance, healthcare, and event-driven systems.
"A Domain Service is a scalpel, not a hammer.
Used wisely, it reveals the hidden muscles of the business."
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.DomainModel— Domain service infrastructure:IDomainService— Marker interface for domain services- Enables dependency injection and testing patterns
- Supports clean architecture separation
ConnectSoft.Extensions.PersistenceModel— Persistence infrastructure:IUnitOfWork— Unit of Work pattern for transactional consistency- Repository interfaces for aggregate persistence
ConnectSoft.Extensions.MessagingModel— Domain events:IEventBus— Event publishing infrastructureIEvent— Base interface for domain events
- Real-World Examples — See
ConnectSoft.Saas.ProductsCatalog.DomainModelfor production implementations:IProductsProcessor/DefaultProductsProcessor— Command domain service exampleIProductsRetriever/DefaultProductsRetriever— Query domain service exampleCreateProductInput,DeleteProductInput— Input type examplesProductCreatedEvent— Domain event example
-
ConnectSoft Internal Standards
- ConnectSoft Domain Layer Architecture Guidelines
- ConnectSoft Domain Service Modeling Playbook
- ConnectSoft Clean Architecture Microservices Blueprint