Skip to content

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
Hold "Alt" / "Option" to enable pan & zoom

✅ 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
Hold "Alt" / "Option" to enable pan & zoom

✅ 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 IDomainService can 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 NamingCreateProductInput, 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 WorkExecuteTransactional() 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
Hold "Alt" / "Option" to enable pan & zoom

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 infrastructure
      • IEvent — Base interface for domain events
    • Real-World Examples — See ConnectSoft.Saas.ProductsCatalog.DomainModel for production implementations:
      • IProductsProcessor / DefaultProductsProcessor — Command domain service example
      • IProductsRetriever / DefaultProductsRetriever — Query domain service example
      • CreateProductInput, DeleteProductInput — Input type examples
      • ProductCreatedEvent — Domain event example
  • ConnectSoft Internal Standards

    • ConnectSoft Domain Layer Architecture Guidelines
    • ConnectSoft Domain Service Modeling Playbook
    • ConnectSoft Clean Architecture Microservices Blueprint