Giter Club home page Giter Club logo

servicecomposer.aspnetcore's Introduction

ServiceComposer

ServiceComposer is a ViewModel Composition Gateway.

Designing a UI when the back-end system consists of dozens (or more) of (micro)services is challenging. We have separation and autonomy on the back end, but this all needs to come back together on the front-end. ViewModel Composition stops it from turning into a mess of spaghetti code and prevents simple actions from causing an inefficient torrent of web requests.

Contents

Technical introduction

For a technical introduction and an overview of the problem space, refer to the following presentation on YouTube.

Getting Started

Imagine an elementary e-commerce web page where it's needed to display details about a selected product. These details are stored in two different services. The Sales service owns the product price, and the Marketing service owns the product name and description. ServiceComposer solves the problem of composing information from different services into one composed view model that downstream clients can later display or consume.

To start using ServiceComposer, follow the outlined steps:

  • Create a .NET 6 or later empty web application project named CompositionGateway in an empty or existing solution.
  • Add a package reference to the ServiceComposer.AspNetCore NuGet package and configure the Startup class as follows:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRouting();
        services.AddViewModelComposition();
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        app.UseRouting();
        app.UseEndpoints(builder => builder.MapCompositionHandlers());
    }
}

snippet source | anchor

  • Add a new .NET 6 or later class library project named Sales.ViewModelComposition.
  • Add a package reference to the ServiceComposer.AspNetCore NuGet package.
  • Add a new class to create a composition request handler.
  • Define the class similar to the following:

public class SalesProductInfo : ICompositionRequestsHandler
{
    [HttpGet("/product/{id}")]
    public Task Handle(HttpRequest request)
    {
        var vm = request.GetComposedResponseModel();

        //retrieve product details from the sales database or service
        vm.ProductId = request.HttpContext.GetRouteValue("id").ToString();
        vm.ProductPrice = 100;

        return Task.CompletedTask;
    }
}

snippet source | anchor

  • Add another class library project, named Marketing.ViewModelComposition, and define a composition request handler like the following:

public class MarketingProductInfo: ICompositionRequestsHandler
{
    [HttpGet("/product/{id}")]
    public Task Handle(HttpRequest request)
    {
        var vm = request.GetComposedResponseModel();

        //retrieve product details from the marketing database or service
        vm.ProductName = "Sample product";
        vm.ProductDescription = "This is a sample product";
        
        return Task.CompletedTask;
    }
}

snippet source | anchor

  • Make it so that the web application project created at the beginning can load both class library assemblies, e.g., by adding a reference to the class library projects
  • Build and run the web application project
  • Using a browser or a tool like Postman, issue an HTTP Get request to <url-of-the-web-application>/product/1

The HTTP response should be a JSON result containing the properties and values defined in the composition handler classes.

NOTE: ServiceComposer uses regular ASP.NET Core attribute routing to configure routes for which composition support is required.

In this brief sample, the view model instance returned by GetComposedResponseModel() is a C# dynamic object. dynamic objects are handy because they allow request handlers to be entirely independent of each other; they share nothing. ServiceComposer supports using strongly typed view models if they are preferred. They have the advantages of strong typing and compiler checks and the disadvantage of a little coupling. Refer to the view model factory documentation for more information.

Documentation and supported platforms

ServiceComposer is available for the following platforms:

Philosophy

Service Boundaries

When building systems based on SOA principles, service boundaries are key, if not THE key aspect. If we get service boundaries wrong, the end result risks being a distributed monolith in the best case and a complete failure in the worst case.

Service boundary identification is a challenge on its own; it requires extensive business domain knowledge and confidence in high-level design techniques. Technical challenges, such as the lack of technical solutions to problems foreseen while defining service boundaries, might drive the solution design in the wrong direction.

The transition from the user mental model, described by domain experts, to the service boundaries architectural model in the SOA space raises many different concerns. If domain entities, as defined by domain experts, are split among several services:

  • how can we then display to users what they need to visualize?
  • when systems need to make decisions, how can they “query” data required to make that decision, stored in many different services?

This type of question leads systems to be designed using rich events, not thin ones, to share data between services and with cache-like things, such as Elastic Search, to satisfy UI query/visualization needs.

This is the beginning of a road that can only lead to a distributed monolith, where data ownership is a lost concept and every change impacts and breaks the whole system. In such a scenario, it’s easy to blame SOA and the toolset.

ViewModel Composition techniques are designed to address all these concerns. They bring the separation of concerns, designed at the back end, to the front end.

For more details and the philosophy behind a Composition Gateway, refer to the ViewModel Composition series of article available on milestone.topics.it.

servicecomposer.aspnetcore's People

Contributors

actions-user avatar adamralph avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar github-actions[bot] avatar mauroservienti avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

servicecomposer.aspnetcore's Issues

Release 1.8.0

  • tag the repo
  • create draft release notes
  • push to Nuget
  • publish release notes
  • close this issue
  • close the milestone

Users are forced to implement an interface to define composition handlers and are constrained to create multiple classes instead of defining multiple methods in the same composition handler class

Depends on:

Add support for conventions, if a class:

  • is defined in a CompositionHandlers namespace
  • has a method, whose name doesn't really matter, that takes the correct argument and returns the correct type
  • has the expected Http*Attribute on the identified method

Then it's a composition handler. And stop making an interface a requirement. This could enable having handlers with multiple methods to group composition actions by use case for example.

Composition Context support

it would be good to move away from using the dynamic view model as the composition context and the data storage. Having a separate ICompositionContext interface allows for a strongly typed RaiseEvent (or event better RaiseEventAsync). The composition context will also be the one keeping track of subscriptions.

The composition context could be introduced using an extension method on the current HTTP request or as an argument to the Handle/Subscribe methods (it's a horrible breaking change complex to introduce gradually)

Allow ViewModel factories to return null to signal to walk up the factory resolution hierarchy

If an endpoint scoped factory returns null, and a global one is available it'll be used instead. If also the latter returns null the default ServiceComposer dynamic view model is created

If an IEndpointScopedViewModelFactory returns null, the next invoked (if defined) if a IViewModelFactory; If it returns null the regular DynamicViewModel is used.

  • implementation
  • tests
  • documentation

Release 2.0.0

  • remove the deprecated target framework
  • remove Snippets.NetCore2x snippets and the docs/asp-net-core-2x folder
  • move tests that need/can to be moved from the ServiceComposer.AspNetCore.Tests project to the ServiceComposer.AspNetCore.Endpoints.Tests project
  • delete the ServiceComposer.AspNetCore.Tests project
  • rename ServiceComposer.AspNetCore.Endpoints.Tests to ServiceComposer.AspNetCore.Tests
  • rename Snippets.NetCore3x to Snippets
  • Update the MVC repo readme to explain why it’s not anymore needed
  • Archive the MVC repo

Release steps

  • tag the repo
  • create draft release notes
  • push to Nuget
  • publish release notes
  • close this issue
  • close the milestone

Release 1.9.0

  • tag the repo
  • create draft release notes
  • Credits to Mark
  • publish release notes
  • push to Nuget
  • close this issue
  • close the milestone

General documentation

Simple documentation structure using markdown files in a /docs subdirectory and compiling it using MarkdownSnippet through a GitHub Action

Allow to customize default Json serialization options

In #240 and #241, we provided options to customize json serialization settings on a per request basis. The default options (that are dependant on the request/response casing) are still hardcoded.

The composition infrastructure should allow customizing default Json serialization settings.

Merging a dynamic into the view model

Hi there

I've been testing Service Composer and ran into a small issue.

The 'viewmodel' property used in the query handlers is not an ExpandoObject, it is of type ServiceComposer.DynamicViewModel. This hides the 'properties' dictionary in a private field in an internal class. The issue is that I have a dynamic already returned from a call to a nosql database. I can't assign this directly to the viewmodel (ie, vm = noSqlResponse), so I would like to iterate over the keys in the dictionary and add the new properties to the vm. Usually with an ExpandoObject I would do something like:

        dynamic thing1 = new ExpandoObject();
        dynamic thing2 = new ExpandoObject();
        thing1.Bar = "Foo";
        thing2.Foo = "Bar";
        Merge(thing1, thing2);
        //result: thing2 now contains .Bar and .Foo

        private void Merge(IDictionary<string, object> from, IDictionary<string, object> to)
        {
            foreach (var key in from.Keys)
                to.Add(new KeyValuePair<string, object>(key, from[key]));
        }

But since I can't access the dictionary behind the DynamicViewModel I can't add new records. Likewise, if I try to set a property directly at runtime (ie. not using the 'vm.NewProperty = 'Foo' syntax)

((IDictionary<String, Object>) vm)[propKey] = propValue;

I get an exception that type DynamicViewModel can't be cast to type IDictionary, which isn't a surprise.

So it seems that I'm pretty well locked out of DynamicViewModel. As a workaround I can assign a temporary value like "vm.response = new ExpandoObject()" and then assign my noSqlResponse. That works, but it's a bit odd to nest all returned data under a single property - I'd much rather just return the data on the root.

Any suggestions on how I might access the properties dictionary in DynamicViewModel to add new properties at runtime to the view model root?

Thanks.

Upgrade guide from 1.x to 2.x

  • deprecated things
  • from IHandleRequest to ICompositionRequestHandler and attribute routing
  • from dynamic view model to ICompositionContext

Add support for Endpoints

Replaces ServiceComposer/ServiceComposer.AspNetCore.Mvc#15

At configuration time:

  • For each "MVC/WebAPI" registered endpoint scan all found "composition handlers" looking for the ones with the same template.
  • Add to the endpoint metadata new custom attribute (that implements IAsyncResultFilter) with the list of types matching the template

At runtime:

  • if the Model returned by the executed controller is empty
    • execute the composition
    • set the Model to the composed result

In theory, since endpoints are available regardless of MVC, all this could be moved to ServiceComposer.AspNetCore, and this package could be downgraded to support only ASP.Net Core 2.x and deprecated.

Support ICompositionRequestsHandler<TRequest>?

Hi @mauroservienti . This is more a question or topic for discussion than an issue.

I've been experimenting with MVC's model binding and if I could make use of it to avoid custom code for extracting route parameters, query string values, body or form content, etc. The AspNetCore ControllerBase has a TryUpdateModel that looked promising so I've written a ModelBinder implementation that replicates most of that method's core function.

This allows me to write the following:

[HttpGet("path/{id:guid}")]
public async Task Handle(HttpRequest request)
{
    var requestModel = await _modelBinder.BindAsync<RequestModel>(request.HttpContext);
    ...
}

where RequestModel is a simple DTO that uses [FromRoute], [FromQuery], etc, to drive the model binding. Same as controller actions basically.

What I really want to do though is just implement something like ICompositionRequestsHandler<MyRequestModel> and have the Handle method include a MyRequestModel parameter, already pre-bound. :-)

Any thoughts on how we might provide some extensibility to support automatic binding step?

View model factories and preview handlers cannot access the composition context and cannot raise events

Current order is:

  • retrieve RequestId from HttpRequest
    • create a new one if not found
  • Add RequestId to the response
  • create the composition context
  • invoke the view model factory if present
  • set the composition context and view model into the context items
  • invoke the composition process
    • preview
    • subscribe
    • handle

The issue is that view model factories cannot access the composition context, it's not yet available. If we make it available then they cannot raise events because there are no subscribers yet. Ideally:

  • retrieve RequestId from HttpRequest
    • create a new one if not found
  • create the composition context
  • set the composition context and view model into the context items
  • Add RequestId to the response
  • Allow subscribers to subscribe
  • invoke the view model factory if present
  • invoke the composition process
    • preview
    • subscribe
    • handle

JsonSerializerSettings used by CompositionEndpointBuilder not surfaced via options

I have a handler that directly queries a database for its response data to be added to the viewmodel. The queries surface EFCore NetTopologySuite data types direct from the DB, such as Point, Coordinate, etc. In order to serialize these types (for the wire) the serializer settings (whether NewtonSoft or System.Text.Json) require a NetTopologySuite custom converter to be registered with the serializer. However, in this package's case we can't access the JsonSerializerSettings that's used when serializing the response.

I understand the current use case is to encapsulate the handling of "accept-casing" but is there possibly a way it can be modified to surface the settings so other configuration can be applied? I can imagine other serialization use cases where this might be important too, such as NullValueHandling or reference loop handling.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.