Turn on the motors
We spent the last seven posts introducing ViewModel Composition and diving into many aspects of it. A detailed list of articles can be found in the ViewModel Composition category.
In a few occasions I mentioned either the infrastructure
or the engine
and always said: “there will be a post about it”. Time has come.
Looking for a ViewModel Composition framework? Take a look at the open source ServiceComposer.AspNetCore.
Composition Gateway
To make sure that data can be aggregated we need to make clients interact with a third party and not directly with endpoints owned by services. Still using our services sample clients don’t directly talk to Marketing, Sales, Warehouse, and Shipping. They connect to a service that sits between them and business services to run the composition logic.
This has also the nice side effect of masquerading the internal services topology. It solves spatial coupling issues.
This service behaves like a reverse proxy:
- receives incoming requests
- puts them on hold
- invokes request handlers
- returns the composed view model responding to the incoming requests
Please welcome ASP.Net Core
ASP.Net Core is a real work of art, the tiny thing I probably love the most is that Routing
is not anymore part of Mvc
. In ASP.Net the routing engine is double linked to Mvc
and has near to zero extension options. In ASP.Net Core Routing
is a separate component/package from Mvc
and can be used in isolation in an ASP.Net Core application that doesn’t use Mvc
.
Given the premise the following is all what is needed to run a sample Composition Gateway:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
services.AddViewModelComposition();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.RunCompositionGatewayWithDefaultRoutes();
}
}
The above startup
“simply” does 3 things:
- Adds
Routing
to the services that will be run by the ASP.Net Core application - Through calling
AddViewModelComposition
:- Scans all assemblies in the
bin
directory looking for types that implement either theIHandleRequests
interface or theISubscribeToCompositionEvents
one - Registers all types matching the above “query” in the current IoC/DI container
- Scans all assemblies in the
- Runs the Composition Gateway as an ASP.Net Core pipeline terminator in
RunCompositionGatewayWithDefaultRoutes
I know it doesn’t really appear simple, have faith. It’s not really complex either.
HTTP Requests handling
ASP.Net Core handles HTTP requests through a pipeline, “elements” in the handling pipeline are called middleware. Middleware can be passthrough, when they intercept the request and then pass it to the next middleware in the pipeline; or they can be terminators as they will terminate the incoming request handling and are responsible to start the response.
Basically there can be only 1 terminator in a pipeline. For example in in
Mvc
we could considerControllers
to be terminators. It’s not that simple, but you probably get the point.More on ASP.Net Core pipeline and middleware in https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/
The Composition Gateway acts like a terminator, it sits at the end of the pipeline. RunCompositionGatewayWithDefaultRoutes
is defined as follows:
public static void RunCompositionGatewayWithDefaultRoutes(this IApplicationBuilder app)
{
app.RunCompositionGateway(routes =>
{
routes.MapComposableGet( template: "{controller}/{id:int?}");
routes.MapRoute("{*NotFound}", context =>
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
});
});
}
What it does is:
- defines a default route using as template
{controller}/{id:int?}
- defines a catch-all route to return a
404
to clients hitting invalid routes
It’s a shortcut to:
public static void RunCompositionGateway(this IApplicationBuilder app, Action<IRouteBuilder> routes = null)
{
var routeBuilder = new RouteBuilder(app);
routes?.Invoke(routeBuilder);
app.UseRouter(routeBuilder.Build());
}
In a real world application we would probably call
RunCompositionGateway
configuring routes we need. We could say thatRunCompositionGatewayWithDefaultRoutes
is for simple scenarios or demo purposes.
Here is when the new routing engine shines. Finally custom routes and route handlers can be defined and injected into the engine. That’s exactly what we are doing in MapComposableGet
. MapComposableGet
calls a more generic MapComposableRoute
defined as follows:
public static IRouteBuilder MapComposableRoute(this IRouteBuilder routeBuilder,
string template,
IDictionary<string, object> constraints,
RouteValueDictionary defaults = null,
RouteValueDictionary dataTokens = null)
{
var route = new Route(
target: new RouteHandler(ctx => HandleRequest(ctx)),
routeTemplate: template,
defaults: defaults,
constraints: constraints,
dataTokens: dataTokens,
inlineConstraintResolver: routeBuilder.ServiceProvider.GetRequiredService<IInlineConstraintResolver>()
);
routeBuilder.Routes.Add(route);
return routeBuilder;
}
We are defining a custom route using the supplied template
and constraints. The relevant line of code is the following:
target: new RouteHandler(ctx => HandleRequest(ctx))
where a custom route handler is defined. Finally, in the custom route handler is where composition happens.
HandleRequest
does 5 things:
- Retrieves from the
IoC/DI
container all instances implementing eitherIHandleRequests
orISubscribeToCompositionEvents
- Filters out, calling
Matches
, all the one not interested in handling the current request - Iterates over all
ISubscribeToCompositionEvents
giving them the opportunity toSubscribe
to events - Iterates over
IHandleRequests
invokingHandle
- Once composition is completed, returns the composed ViewModel to the client.
Conclusion
Before composition happens many moving parts are involved. It seems complex at the beginning, but once it’s clear that the behavior is not that different from a regular reverse proxy it’s probably easier to find a place for each one of the moving parts. I crafted a simple Composition Gateway implementation and a reference sample implementation. It’s available on my GitHub account in the composition-gateway-sample repository.
Header image: Photo by Bruce Warrington on Unsplash