-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#218 - Move everything required for domain to the new API project.
- Loading branch information
Showing
14 changed files
with
1,059 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Identity; | ||
using Microsoft.Extensions.Configuration; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Money.Commands; | ||
using Money.Commands.Handlers; | ||
using Money.Data; | ||
using Money.Hubs; | ||
using Money.Models; | ||
using Money.Services; | ||
using Money.Users.Commands.Handlers; | ||
using Money.Users.Models; | ||
using Neptuo; | ||
using Neptuo.Activators; | ||
using Neptuo.Bootstrap; | ||
using Neptuo.Commands; | ||
using Neptuo.Converters; | ||
using Neptuo.Data; | ||
using Neptuo.Events; | ||
using Neptuo.Exceptions.Handlers; | ||
using Neptuo.Formatters; | ||
using Neptuo.Formatters.Converters; | ||
using Neptuo.Formatters.Metadata; | ||
using Neptuo.Logging; | ||
using Neptuo.Logging.Serialization; | ||
using Neptuo.Models.Repositories; | ||
using Neptuo.Models.Snapshots; | ||
using Neptuo.Queries; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Money.Bootstrap | ||
{ | ||
public class BootstrapTask : IBootstrapTask | ||
{ | ||
private readonly IServiceCollection services; | ||
private readonly ConnectionStrings connectionStrings; | ||
|
||
private ILogFactory logFactory; | ||
private ILog errorLog; | ||
private IFactory<ReadModelContext> readModelContextFactory; | ||
private IFactory<EventSourcingContext> eventSourcingContextFactory; | ||
|
||
private PriceCalculator priceCalculator; | ||
private ICompositeTypeProvider typeProvider; | ||
|
||
private ExceptionHandlerBuilder exceptionHandlerBuilder; | ||
|
||
private DefaultQueryDispatcher queryDispatcher; | ||
private PersistentCommandDispatcher commandDispatcher; | ||
private PersistentEventDispatcher eventDispatcher; | ||
|
||
private EntityEventStore eventStore; | ||
|
||
private IFormatter commandFormatter; | ||
private IFormatter eventFormatter; | ||
private IFormatter queryFormatter; | ||
private IFormatter exceptionFormatter; | ||
|
||
public BootstrapTask(IServiceCollection services, ConnectionStrings connectionStrings) | ||
{ | ||
Ensure.NotNull(services, "services"); | ||
Ensure.NotNull(connectionStrings, "connectionStrings"); | ||
this.services = services; | ||
this.connectionStrings = connectionStrings; | ||
} | ||
|
||
public void Initialize() | ||
{ | ||
logFactory = new DefaultLogFactory("Root").AddSerializer(new ConsoleSerializer()); | ||
errorLog = logFactory.Scope("Error"); | ||
|
||
readModelContextFactory = Factory.Getter(() => new ReadModelContext(connectionStrings.ReadModel)); | ||
eventSourcingContextFactory = Factory.Getter(() => new EventSourcingContext(connectionStrings.EventSourcing)); | ||
CreateReadModelContext(); | ||
CreateEventSourcingContext(); | ||
|
||
exceptionHandlerBuilder = new ExceptionHandlerBuilder(); | ||
|
||
services | ||
.AddSingleton(readModelContextFactory) | ||
.AddSingleton(eventSourcingContextFactory) | ||
.AddSingleton(exceptionHandlerBuilder) | ||
.AddSingleton<IExceptionHandler>(exceptionHandlerBuilder); | ||
|
||
Domain(); | ||
|
||
priceCalculator = new PriceCalculator(eventDispatcher.Handlers, queryDispatcher); | ||
|
||
services | ||
.AddSingleton(priceCalculator) | ||
.AddSingleton(new FormatterContainer(commandFormatter, eventFormatter, queryFormatter, exceptionFormatter)); | ||
|
||
ReadModels(); | ||
|
||
services | ||
.AddSingleton<IEventHandlerCollection>(eventDispatcher.Handlers) | ||
.AddScoped<ICommandDispatcher>(provider => new UserCommandDispatcher(commandDispatcher, provider.GetService<IHttpContextAccessor>().HttpContext, provider.GetService<ApiHub>())) | ||
.AddScoped<IQueryDispatcher>(provider => new UserQueryDispatcher(queryDispatcher, provider.GetService<IHttpContextAccessor>().HttpContext)); | ||
|
||
CurrencyCache currencyCache = new CurrencyCache(eventDispatcher.Handlers, queryDispatcher, queryDispatcher); | ||
|
||
services | ||
.AddSingleton(currencyCache); | ||
} | ||
|
||
private void Domain() | ||
{ | ||
Converts.Repository | ||
.AddStringTo<int>(Int32.TryParse) | ||
.AddStringTo<bool>(Boolean.TryParse) | ||
.AddEnumSearchHandler(false) | ||
.AddJsonEnumSearchHandler() | ||
.AddJsonPrimitivesSearchHandler() | ||
.AddJsonObjectSearchHandler() | ||
.AddJsonKey() | ||
.AddJsonTimeSpan() | ||
.Add(new ColorConverter()) | ||
.AddToStringSearchHandler(); | ||
|
||
eventStore = new EntityEventStore(eventSourcingContextFactory); | ||
eventDispatcher = new PersistentEventDispatcher(new EmptyEventStore()); | ||
eventDispatcher.DispatcherExceptionHandlers.Add(exceptionHandlerBuilder); | ||
eventDispatcher.EventExceptionHandlers.Add(exceptionHandlerBuilder); | ||
|
||
IFactory<ICompositeStorage> compositeStorageFactory = Factory.Default<JsonCompositeStorage>(); | ||
|
||
typeProvider = new ReflectionCompositeTypeProvider( | ||
new ReflectionCompositeDelegateFactory(), | ||
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | ||
); | ||
|
||
commandFormatter = new CompositeCommandFormatter(typeProvider, compositeStorageFactory); | ||
eventFormatter = new CompositeEventFormatter(typeProvider, compositeStorageFactory, new List<ICompositeFormatterExtender>() { new UserKeyEventExtender() }); | ||
queryFormatter = new CompositeListFormatter(typeProvider, compositeStorageFactory, logFactory); | ||
exceptionFormatter = new CompositeExceptionFormatter(typeProvider, compositeStorageFactory); | ||
|
||
commandDispatcher = new PersistentCommandDispatcher(new SerialCommandDistributor(), new EmptyCommandStore(), commandFormatter); | ||
commandDispatcher.DispatcherExceptionHandlers.Add(exceptionHandlerBuilder); | ||
commandDispatcher.CommandExceptionHandlers.Add(exceptionHandlerBuilder); | ||
|
||
queryDispatcher = new DefaultQueryDispatcher(); | ||
|
||
var outcomeRepository = new AggregateRootRepository<Outcome>( | ||
eventStore, | ||
eventFormatter, | ||
new ReflectionAggregateRootFactory<Outcome>(), | ||
eventDispatcher, | ||
new NoSnapshotProvider(), | ||
new EmptySnapshotStore() | ||
); | ||
|
||
var categoryRepository = new AggregateRootRepository<Category>( | ||
eventStore, | ||
eventFormatter, | ||
new ReflectionAggregateRootFactory<Category>(), | ||
eventDispatcher, | ||
new NoSnapshotProvider(), | ||
new EmptySnapshotStore() | ||
); | ||
|
||
var currencyListRepository = new AggregateRootRepository<CurrencyList>( | ||
eventStore, | ||
eventFormatter, | ||
new ReflectionAggregateRootFactory<CurrencyList>(), | ||
eventDispatcher, | ||
new NoSnapshotProvider(), | ||
new EmptySnapshotStore() | ||
); | ||
|
||
Money.BootstrapTask bootstrapTask = new Money.BootstrapTask( | ||
commandDispatcher.Handlers, | ||
Factory.Instance(outcomeRepository), | ||
Factory.Instance(categoryRepository), | ||
Factory.Instance(currencyListRepository) | ||
); | ||
|
||
bootstrapTask.Initialize(); | ||
|
||
UserHandler userHandler = new UserHandler(services.BuildServiceProvider().GetRequiredService<UserManager<ApplicationUser>>(), eventDispatcher); | ||
commandDispatcher.Handlers.AddAll(userHandler); | ||
queryDispatcher.AddAll(userHandler); | ||
} | ||
|
||
private void ReadModels() | ||
{ | ||
Models.Builders.BootstrapTask bootstrapTask = new Models.Builders.BootstrapTask( | ||
queryDispatcher, | ||
eventDispatcher.Handlers, | ||
readModelContextFactory, | ||
priceCalculator | ||
); | ||
|
||
bootstrapTask.Initialize(); | ||
} | ||
|
||
private void CreateEventSourcingContext() | ||
{ | ||
using (var eventSourcing = eventSourcingContextFactory.Create()) | ||
eventSourcing.Database.EnsureCreated(); | ||
} | ||
|
||
private void CreateReadModelContext() | ||
{ | ||
using (var readModels = readModelContextFactory.Create()) | ||
readModels.Database.EnsureCreated(); | ||
} | ||
|
||
public void Handle(Exception exception) | ||
=> errorLog.Error(exception); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
using Microsoft.AspNetCore.Authorization; | ||
using Microsoft.AspNetCore.Mvc; | ||
using Money.Models.Api; | ||
using Money.Services; | ||
using Neptuo; | ||
using Neptuo.Commands; | ||
using Neptuo.Formatters; | ||
using Neptuo.Queries; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Net.Mime; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Money.Controllers | ||
{ | ||
[Authorize] | ||
[Route("[controller]/[action]")] | ||
public class ApiController : Controller | ||
{ | ||
private readonly FormatterContainer formatters; | ||
private readonly ICommandDispatcher commandDispatcher; | ||
private readonly IQueryDispatcher queryDispatcher; | ||
private readonly CommandMapper commandMapper; | ||
private readonly QueryMapper queryMapper; | ||
|
||
public ApiController(FormatterContainer formatters, ICommandDispatcher commandDispatcher, IQueryDispatcher queryDispatcher, CommandMapper commandMapper, QueryMapper queryMapper) | ||
{ | ||
Ensure.NotNull(formatters, "formatters"); | ||
Ensure.NotNull(commandDispatcher, "commandDispatcher"); | ||
Ensure.NotNull(queryDispatcher, "queryDispatcher"); | ||
Ensure.NotNull(commandMapper, "commandMapper"); | ||
Ensure.NotNull(queryMapper, "queryMapper"); | ||
this.formatters = formatters; | ||
this.commandDispatcher = commandDispatcher; | ||
this.queryDispatcher = queryDispatcher; | ||
this.commandMapper = commandMapper; | ||
this.queryMapper = queryMapper; | ||
} | ||
|
||
public string UserName() => HttpContext.User.Identity.Name; | ||
|
||
[HttpPost] | ||
public ActionResult Query([FromBody] Request request) | ||
{ | ||
Ensure.NotNull(request, "request"); | ||
|
||
string payload = request.Payload; | ||
Type type = Type.GetType(request.Type); | ||
|
||
return Query(payload, type); | ||
} | ||
|
||
[HttpPost] | ||
[Route("{*url}")] | ||
public ActionResult Query(string url, [FromBody] string payload) | ||
{ | ||
Ensure.NotNullOrEmpty(url, "url"); | ||
Ensure.NotNullOrEmpty(payload, "payload"); | ||
|
||
Type type = queryMapper.FindTypeByUrl(url); | ||
return Query(payload, type); | ||
} | ||
|
||
private ActionResult Query(string payload, Type type) | ||
{ | ||
Ensure.NotNull(type, "type"); | ||
|
||
object query = formatters.Query.Deserialize(type, payload); | ||
|
||
MethodInfo methodInfo = queryDispatcher.GetType().GetMethod(nameof(queryDispatcher.QueryAsync)); | ||
if (methodInfo != null) | ||
{ | ||
methodInfo = methodInfo.MakeGenericMethod(type.GetInterfaces().First().GetGenericArguments().First()); | ||
Task task = (Task)methodInfo.Invoke(queryDispatcher, new[] { query }); | ||
task.Wait(); | ||
|
||
object output = task.GetType().GetProperty(nameof(Task<object>.Result)).GetValue(task); | ||
if (output != null) | ||
{ | ||
ResponseType responseType = ResponseType.Composite; | ||
type = output.GetType(); | ||
|
||
if (output is string || output is int || output is decimal || output is bool) | ||
{ | ||
payload = output.ToString(); | ||
responseType = ResponseType.Plain; | ||
} | ||
else | ||
{ | ||
payload = formatters.Query.Serialize(output); | ||
} | ||
|
||
HttpContext.Response.ContentType = "text/json"; | ||
return Json(new Response() | ||
{ | ||
Payload = payload, | ||
Type = type.AssemblyQualifiedName, | ||
ResponseType = responseType | ||
}); | ||
} | ||
} | ||
|
||
return NotFound(); | ||
} | ||
|
||
[HttpPost] | ||
public ActionResult Command([FromBody] Request request) | ||
{ | ||
string payload = request.Payload; | ||
Type type = Type.GetType(request.Type); | ||
|
||
return Command(payload, type); | ||
} | ||
|
||
[HttpPost] | ||
[Route("{*url}")] | ||
public ActionResult Command(string url, [FromBody] string payload) | ||
{ | ||
Ensure.NotNullOrEmpty(url, "url"); | ||
Ensure.NotNullOrEmpty(payload, "payload"); | ||
|
||
Type type = commandMapper.FindTypeByUrl(url); | ||
return Command(payload, type); | ||
} | ||
|
||
private ActionResult Command(string payload, Type type) | ||
{ | ||
object command = formatters.Command.Deserialize(type, payload); | ||
|
||
MethodInfo methodInfo = commandDispatcher.GetType().GetMethod(nameof(commandDispatcher.HandleAsync)); | ||
if (methodInfo != null) | ||
{ | ||
methodInfo = methodInfo.MakeGenericMethod(type); | ||
Task task = (Task)methodInfo.Invoke(commandDispatcher, new[] { command }); | ||
task.Wait(); | ||
|
||
return Ok(); | ||
} | ||
|
||
return StatusCode(500); | ||
} | ||
} | ||
} |
Oops, something went wrong.