🔰 1. What is Domain-Driven Design (DDD)?
- Invented by Eric Evans, focused on domain-centered models.
- Separates code into the experts' language (Ubiquitous Language).
- Major advantages:
- Code that is easy to test
- Clear separation of responsibilities
- Long-term logical scalability
📐 2. Structuring the .NET Core solution into layers (projects)
Recommended organization of the solution:
/src
├── Blog.Domain <- models, entities, Value Objects
├── Blog.Application <- application services, commands, DTOs
├── Blog.Infrastructure <- EF Core, repositories, external deps
└── Blog.API <- Web API Controllers, DI, presentation
Explanation of each layer:
• Domain: pure logic, without external dependencies
• Application: orchestration between services, no direct DB access
• Infrastructure: technical details (SQL, Redis, Email)
• API: controllers, authentication, external DTOs
🧱 3. Domain model – Creating the Post aggregate
public class Post : Entity<Guid>
{
public Title Title { get; private set; }
public Slug Slug { get; private set; }
public PostContent Content { get; private set; }
public DateTime PublishedAt { get; private set; }
public Post(Title title, Slug slug, PostContent content)
{
Title = title;
Slug = slug;
Content = content;
PublishedAt = DateTime.UtcNow;
}
}
Value Object: Slug
public class Slug : ValueObject
{
public string Value { get; }
private Slug(string value)
{
Value = value;
}
public static Slug FromTitle(string title)
{
var slug = Regex.Replace(title.ToLower(), @"[^a-z0-9]+", "-");
return new Slug(slug.Trim('-'));
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return Value;
}
}
📦 4. Repository Pattern + Unit of Work
Generic interface:
public interface IRepository<T> where T : class
{
Task<T?> GetByIdAsync(Guid id);
Task AddAsync(T entity);
void Remove(T entity);
}
Unit of Work:
public interface IUnitOfWork
{
Task<int> SaveChangesAsync();
}
In Infrastructure, we will implement PostRepository with EF Core.
💡 5. Application Layer + MediatR
Create post command:
public record CreatePostCommand(string Title, string Content) : IRequest<Guid>;
Handler:
public class CreatePostHandler : IRequestHandler<CreatePostCommand, Guid>
{
private readonly IPostRepository _postRepo;
private readonly IUnitOfWork _uow;
public CreatePostHandler(IPostRepository postRepo, IUnitOfWork uow)
{
_postRepo = postRepo;
_uow = uow;
}
public async Task<Guid> Handle(CreatePostCommand request, CancellationToken ct)
{
var slug = Slug.FromTitle(request.Title);
var post = new Post(new Title(request.Title), slug, new PostContent(request.Content));
await _postRepo.AddAsync(post);
await _uow.SaveChangesAsync();
return post.Id;
}
}
🔚 Conclusion
This article marks the beginning of implementing our backend using DDD in .NET Core. We discussed key concepts, project structure, and implemented a first aggregate (Post) with robust patterns.