Skip to content

Commit

Permalink
#218 - Move everything required for domain to the new API project.
Browse files Browse the repository at this point in the history
  • Loading branch information
maraf committed Mar 2, 2019
1 parent 0f95579 commit baca08c
Show file tree
Hide file tree
Showing 14 changed files with 1,059 additions and 6 deletions.
216 changes: 216 additions & 0 deletions src/Money.Api/Domain/Bootstrap/BootstrapTask.cs
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);
}
}
147 changes: 147 additions & 0 deletions src/Money.Api/Domain/Controllers/ApiController.cs
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);
}
}
}
Loading

0 comments on commit baca08c

Please sign in to comment.