Upgrade to Pro — share decks privately, control downloads, hide ads and more …

.NET Day 2019 - Designing a UI for Microservices by Mauro Servienti

.NET Day 2019 - Designing a UI for Microservices by Mauro Servienti

How do we design a UI when the back-end system consists of dozens (or more) microservices? We have separation and autonomy on the back end, but on the front end this all needs to come back together. How do we stop it from turning into a mess of spaghetti code? How do we prevent simple actions from causing an inefficient torrent of web requests? Join Mauro in building a Composite UI for Microservices from scratch, using .NET Core. Walk away with a clear understanding of what Services UI Composition is and how you can architect front end to be Microservices ready.

dotnetday

May 28, 2019
Tweet

More Decks by dotnetday

Other Decks in Technology

Transcript

  1. All I wanna do when I wake up in the

    morning is… Rosanna, Toto. Toto IV mauroservienti
  2. Domain Model Decomposition services owning their own piece of information

    Single Responsibility Principle mauroservienti
  3. It's a reporting and integration issue • We're crossing a

    “boundary” • Data flow out of each service to the user • Users are already pulling things on demand • let's benefit of that mauroservienti
  4. ViewModel Composition Catalog Sales Shipping Warehouse composition gateway HTTP Request

    -> /products/1 Catalog Handler Sales Handler Shipping Handler Warehouse Handler Request matching HTTP Response -> ViewModel Composition Composed ViewModel mauroservienti Creates empty ViewModel
  5. Request Matching mauroservienti class ProductDetailsGetHandler : IHandleRequests { } public

    bool Matches(RouteData routeData, string httpVerb, HttpRequest request) { } var controller = (string)routeData.Values["controller"]; return HttpMethods.IsGet(httpVerb) && controller.ToLowerInvariant() == "products" && routeData.Values.ContainsKey("id");
  6. Composition mauroservienti class ProductDetailsGetHandler : IHandleRequests { } public bool

    Matches(RouteData routeData, string httpVerb, HttpRequest request) { } public async Task Handle(string requestId, dynamic vm, RouteData routeData, HttpRequest request) { var id = (string)routeData.Values["id"]; var url = $"http://localhost:5002/api/product-details/product/{id}"; var response = await new HttpClient().GetAsync(url); dynamic details = await response.Content.AsExpando(); vm.ProductName = details.Name; vm.ProductDescription = details.Description; }
  7. Composition mauroservienti class ProductDetailsGetHandler : IHandleRequests { } public bool

    Matches(RouteData routeData, string httpVerb, HttpRequest request) { } public async Task Handle(string requestId, dynamic vm, RouteData routeData, HttpRequest request) { var id = (string)routeData.Values["id"]; var url = $"http://localhost:5002/api/product-details/product/{id}"; var response = await new HttpClient().GetAsync(url); dynamic details = await response.Content.AsExpando(); vm.ProductName = details.Name; vm.ProductDescription = details.Description; } retrieve data from your favorite source
  8. Output mauroservienti { "productName": "Banana Holder", "productDescription": "Outdoor travel cute

    banana protector storage box", "productPrice": 10, "productShippingOptions": "Express Delivery, Regular mail", "productInventory": 4, "productOutOfStock": false }
  9. Output mauroservienti { "productName": "Banana Holder", "productDescription": "Outdoor travel cute

    banana protector storage box", "productPrice": 10, "productShippingOptions": "Express Delivery, Regular mail", "productInventory": 4, "productOutOfStock": false } Catalog Sales Shipping Warehouse
  10. ProductNameA € 20.00 cover image A Supplier A ProductNameB €

    20.00 cover image B Supplier B ProductNameC € 20.00 cover image C Supplier C ProductNameD € 20.00 cover image D Supplier D mauroservienti View Model /available/products
  11. Catalog Handler View Model Catalog Handler ProductNameA € 20.00 cover

    image A Supplier A ProductNameB € 20.00 cover image B Supplier B ProductNameC € 20.00 cover image C Supplier C ProductNameD € 20.00 cover image D Supplier D load available products mauroservienti /available/products
  12. client-side message broker Sales Handler “….” Handler Sales Handler “….”

    Handler ProductNameA € 20.00 cover image A Supplier A ProductNameB € 20.00 cover image B Supplier B ProductNameC € 20.00 cover image C Supplier C ProductNameD € 20.00 cover image D Supplier D publish ` AvailableProductsLoaded` receive event mauroservienti Catalog Handler View Model Catalog Handler /available/products receive event
  13. ProductNameA € 20.00 cover image A Supplier A ProductNameB €

    20.00 cover image B Supplier B ProductNameC € 20.00 cover image C Supplier C ProductNameD € 20.00 cover image D Supplier D mauroservienti Sales Handler “….” Handler Sales Handler “….” Handler Catalog Handler View Model Catalog Handler /available/products
  14. Composition mauroservienti class AvailableProductsGetHandler : IHandleRequests { } public async

    Task Handle(string requestId, dynamic vm, RouteData routeData, HttpRequest request) { var url = $"http://localhost:5002/api/available/products"; var client = new HttpClient(); var response = await client.GetAsync(url); var availableProducts = await response.Content.As<int[]>(); var availableProductsViewModel = MapToDictionary(availableProducts); await vm.RaiseEvent(new AvailableProductsLoaded() { AvailableProductsViewModel = availableProductsViewModel }); vm.AvailableProducts = availableProductsViewModel.Values.ToList(); }
  15. Composition mauroservienti var availableProducts = await response.Content.As<int[]>(); var availableProductsViewModel =

    MapToDictionary(availableProducts); await vm.RaiseEvent(new AvailableProductsLoaded() { AvailableProductsViewModel = availableProductsViewModel }); vm.AvailableProducts = availableProductsViewModel.Values.ToList();
  16. Composition class AvailableProductsLoadedSubscriber : ISubscribeToCompositionEvents { } public void Subscribe(IPublishCompositionEvents

    publisher) { publisher.Subscribe<AvailableProductsLoaded>(async (requestId, pageViewModel, @event, routeData, request) => { }); } mauroservienti
  17. Composition } async (requestId, pageViewModel, @event, routeData, request) => {

    var ids = string.Join(",", @event.AvailableProductsViewModel.Keys); var url = $"http://localhost:5001/api/prices/products/{ids}"; var client = new HttpClient(); var response = await client.GetAsync(url); dynamic[] productPrices = await response.Content.AsExpandoArray(); foreach (dynamic pp in productPrices) { @event.AvailableProductsViewModel[(int)productPrice.Id].ProductPrice = pp.Price; } }); mauroservienti
  18. Output mauroservienti { "avalableProducts": [{ "id": 1, "productName": "Banana Holder",

    "productDescription": "Outdoor travel cute banana protector storage box", "productPrice": 10, "inventory": 4, },{ "id": 2, "productName": "Nokia Lumia 635", "…": "…", }] }
  19. Output mauroservienti { "avalableProducts": [{ "id": 1, "productName": "Banana Holder",

    "productDescription": "Outdoor travel cute banana protector storage box", "productPrice": 10, "inventory": 4, },{ "id": 2, "productName": "Nokia Lumia 635", "…": "…", }] } Catalog Sales Warehouse
  20. ViewModel Composition Catalog Sales Shipping Warehouse composition gateway HTTP Request

    -> /products/1 Catalog Handler Sales Handler Shipping Handler Warehouse Handler HTTP Response -> ViewModel mauroservienti Request matching Composition Composed ViewModel Creates empty ViewModel
  21. Composed ViewModel ViewModel Composition Catalog Sales Shipping Warehouse composition gateway

    HTTP Request -> /products/1 HTTP Response -> ViewModel mauroservienti Creates empty ViewModel Request matching Composition Catalog Handler Sales Handler Shipping Handler Warehouse Handler
  22. Vertical ownership Catalog Sales Shipping Warehouse Doc DB + HTTP

    DB DB back-end back-end Web Proxy API API Proxy to 3° party API 1 month cache 24 hours cache No cache 10 minutes cache User Interface mauroservienti
  23. Vertical ownership Catalog Sales Shipping Warehouse Doc DB + HTTP

    DB DB back-end back-end Web Proxy API API Proxy to 3° party API User Interface composition gateway Catalog Handler Sales Handler Shipping Handler Warehouse Handler mauroservienti
  24. The elephant in the room Catalog Sales Shipping Warehouse Doc

    DB + HTTP DB DB back-end back-end Web Proxy API API Proxy to 3° party API User Interface composition gateway Catalog Handler Sales Handler Shipping Handler Warehouse Handler mauroservienti
  25. Branding • It's the technical authority owing the UI contract

    • Services that want to display data implement it • UI structure is defined as a “monolith” mauroservienti
  26. <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">@Model.ProductDescription</h3> </div> <div class="panel-body">

    <div>Price: @Model.ProductPrice</div> <div>Shipping Options: @Model.ProductShippingOptions</div> <div>Inventory: @Model.ProductInventory</div> </div> </div> mauroservienti
  27. UI Composition •UI defines only high level structure •Highly coupled

    with the presentation technology •Branding is less important •The more “magic” the less Branding •Services own the whole vertical slice mauroservienti
  28. <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">@Model.ProductDescription</h3> </div> <div class="panel-body">

    @await Component.InvokeAsync("ProductDetailsPrice") @await Component.InvokeAsync("ProductDetailsShippingOptions") @await Component.InvokeAsync("ProductDetailsInventory") </div> </div> mauroservienti
  29. <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">@Model.ProductDescription</h3> </div> <div class="panel-body">

    @await Component.InvokeAsync("ProductDetailsPrice") @await Component.InvokeAsync("ProductDetailsShippingOptions") @await Component.InvokeAsync("ProductDetailsInventory") </div> </div> mauroservienti Catalog Sales Shipping Warehouse
  30. Full vertical ownership mauroservienti Catalog Sales Shipping Warehouse Doc DB

    + HTTP DB DB back-end back-end Web Proxy API API Proxy to 3° party API User Interface composition gateway Catalog Handler Sales Handler Shipping Handler Warehouse Handler
  31. Full vertical ownership client shell mauroservienti Catalog Components Sales Components

    Shipping Components Warehouse Components Catalog Sales Shipping Warehouse Doc DB + HTTP DB DB back-end back-end Web Proxy API API Proxy to 3° party API composition gateway Catalog Handler Sales Handler Shipping Handler Warehouse Handler
  32. Every year is getting shorter never seem to find the

    time… ViewModel Composition Demos bit.ly/dnd19-composition Udi Dahan about Service Modelling go.particular.net/dnd19-composition mauroservienti
  33. Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition Is it worth the effort? •

    Complexity increases as “black magic” kicks in • For large projects with a small UI team a traditional monolithic UI approach might be better mauroservienti
  34. Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition Is it worth the effort? •

    Complexity increases as “black magic” kicks in • It’s simpler to break a monolithic UI later • Than to manage not needed complexity mauroservienti
  35. Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition Takeaways • Boundaries are key to

    success • Do not bring in more technology to solve non- technical problems mauroservienti
  36. • Boundaries are key to success • Mental model can

    badly influence design • Users/Business analysts tend to think in term of data presentation mauroservienti Takeaways Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition
  37. Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition Takeaways • Boundaries are key to

    success • Mental model can badly influence design • Use ViewModel composition to present data • No need for complex projections and read models mauroservienti
  38. Mauro Servienti Solution Architect @ Particular Software the makers of

    NServiceBus [email protected] @mauroservienti //github.com/mauroservienti //milestone.topics.it mauroservienti
  39. Thank you! Join me at the Particular booth Demos: bit.ly/dnd19-composition

    Videos: go.particular.net/dnd19-composition mauroservienti