Commit d6656043 authored by Steve Smith's avatar Steve Smith

Updated with domain event support (mostly complete)

parent 394735d6
......@@ -15,6 +15,11 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CleanArchitecture.Tests", "
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CleanArchitecture.Web", "src\CleanArchitecture.Web\CleanArchitecture.Web.xproj", "{8EF3A9A7-6901-402A-B128-0072DA7BC880}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6A0F8671-8BB3-4DB7-80AF-994E4EDA08EC}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......
{
"projects": [ "src" ]
}
\ No newline at end of file
using CleanArchitecture.Core.Model;
namespace CleanArchitecture.Core.Events
{
public class ToDoItemCompletedEvent : BaseDomainEvent
{
public ToDoItem CompletedItem { get; set; }
public ToDoItemCompletedEvent(ToDoItem completedItem)
{
CompletedItem = completedItem;
}
}
}
\ No newline at end of file
using CleanArchitecture.Core.Model;
namespace CleanArchitecture.Core.Interfaces
{
public interface IDomainEventDispatcher
{
void Dispatch(BaseDomainEvent domainEvent);
}
}
\ No newline at end of file
using CleanArchitecture.Core.Model;
namespace CleanArchitecture.Core.Interfaces
{
public interface IHandle<T> where T : BaseDomainEvent
{
void Handle(T domainEvent);
}
}
\ No newline at end of file
using System.Collections.Generic;
using CleanArchitecture.Core.Model;
namespace CleanArchitecture.Core.Interfaces
{
public interface IRepository<T> where T : BaseEntity
{
T GetById(int id);
List<T> List();
T Add(T entity);
void Update(T entity);
void Delete(T entity);
}
}
\ No newline at end of file
using System;
namespace CleanArchitecture.Core.Model
{
public abstract class BaseDomainEvent
{
public DateTime DateOccurred { get; protected set; } = DateTime.Now;
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace CleanArchitecture.Core.Model
{
public abstract class BaseEntity
{
public int Id { get; set; }
public List<BaseDomainEvent> Events = new List<BaseDomainEvent>();
}
}
\ No newline at end of file
using CleanArchitecture.Core.Events;
namespace CleanArchitecture.Core.Model
{
public class ToDoItem : BaseEntity
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsDone { get; private set; } = false;
public void MarkComplete()
{
IsDone = true;
Events.Add(new ToDoItemCompletedEvent(this));
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.Model;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace CleanArchitecture.Infrastructure.Data
{
public class AppDbContext : DbContext
{
private readonly IDomainEventDispatcher _dispatcher;
public AppDbContext(DbContextOptions<AppDbContext> options, IDomainEventDispatcher dispatcher)
: base(options)
{
_dispatcher = dispatcher;
}
public DbSet<ToDoItem> ToDoItems { get; set; }
public override int SaveChanges()
{
var entitiesWithEvents = ChangeTracker.Entries<BaseEntity>()
.Select(e => e.Entity)
.Where(e => e.Events.Any())
.ToArray();
foreach (var entity in entitiesWithEvents)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in entity.Events)
{
_dispatcher.Dispatch(domainEvent);
}
}
return base.SaveChanges();
}
}
public class EfRepository<T> : IRepository<T> where T : BaseEntity
{
private readonly AppDbContext _dbContext;
public EfRepository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public T GetById(int id)
{
return _dbContext.Set<T>().FirstOrDefault(e => e.Id == id);
}
public List<T> List()
{
return _dbContext.Set<T>().ToList();
}
public T Add(T entity)
{
if (entity.Id == 0)
{
int newId = 1;
var entities = List();
if (entities.Any())
{
newId = entities.Max(z => z.Id) + 1;
}
entity.Id = newId;
}
_dbContext.Set<T>().Add(entity);
_dbContext.SaveChanges();
return entity;
}
public void Delete(T entity)
{
_dbContext.Set<T>().Remove(entity);
_dbContext.SaveChanges();
}
public void Update(T entity)
{
_dbContext.Entry(entity).State = EntityState.Modified;
_dbContext.SaveChanges();
}
}
}
\ No newline at end of file
using System;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.Model;
using StructureMap;
using System.Linq;
namespace CleanArchitecture.Infrastructure.DomainEvents
{
// https://gist.github.com/jbogard/54d6569e883f63afebc7
// http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/
public class DomainEventDispatcher : IDomainEventDispatcher
{
private readonly IContainer _container;
public DomainEventDispatcher(IContainer container)
{
_container = container;
}
public void Dispatch(BaseDomainEvent domainEvent)
{
var handlerType = typeof(IHandle<>).MakeGenericType(domainEvent.GetType());
var wrapperType = typeof(DomainEventHandler<>).MakeGenericType(domainEvent.GetType());
var handlers = _container.GetAllInstances(handlerType);
var wrappedHandlers = handlers
.Cast<object>()
.Select(handler => (DomainEventHandler)Activator.CreateInstance(wrapperType, handler));
foreach (var handler in wrappedHandlers)
{
handler.Handle(domainEvent);
}
}
private abstract class DomainEventHandler
{
public abstract void Handle(BaseDomainEvent domainEvent);
}
private class DomainEventHandler<T> : DomainEventHandler
where T : BaseDomainEvent
{
private readonly IHandle<T> _handler;
public DomainEventHandler(IHandle<T> handler)
{
_handler = handler;
}
public override void Handle(BaseDomainEvent domainEvent)
{
_handler.Handle((T)domainEvent);
}
}
}
}
\ No newline at end of file
......@@ -2,7 +2,14 @@
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.6.0"
"NETStandard.Library": "1.6.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer.Design": {
"version": "1.0.0",
"type": "build"
},
"StructureMap.Microsoft.DependencyInjection": "1.2.0",
"CleanArchitecture.Core": "1.0.0-*"
},
"frameworks": {
......
using Microsoft.AspNetCore.Builder;
using CleanArchitecture.Infrastructure.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
......@@ -26,8 +28,8 @@ namespace CleanArchitecture.Web
// Add framework services.
// TODO: Add DbContext and IOC
// services.AddDbContext<ApplicationDbContext>(options =>
//options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
......
{
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
......@@ -13,13 +13,16 @@
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0"
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
"CleanArchitecture.Core": "1.0.0-*",
"CleanArchitecture.Infrastructure": "1.0.0-*"
},
"tools": {
......
{
{
"version": "1.0.0-*",
"dependencies": {
"CleanArchitecture.Core": "1.0.0-*",
"NETStandard.Library": "1.6.0"
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment