Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Designing a ui for microservices @ .NET Day Switzerland 2019

269 views

Published on

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.

Published in: Software

Designing a ui for microservices @ .NET Day Switzerland 2019

  1. 1. mauroservienti
  2. 2. designing a UI for Microservices Mauro Servienti mauroservienti
  3. 3. spaghetti trademark is mine mauroservienti
  4. 4. All I wanna do when I wake up in the morning is… Rosanna, Toto. Toto IV mauroservienti
  5. 5. Buy a "Banana Protector" mauroservienti
  6. 6. Does a page like that exist? mauroservienti
  7. 7. There is no "Banana Protector" Sales Warehouse Shipping Catalog mauroservienti
  8. 8. Domain Model Decomposition services owning their own piece of information Single Responsibility Principle mauroservienti
  9. 9. How can we design something like that? mauroservienti
  10. 10. Denormalization Temptations… Catalog Sales Shipping Warehouse De-normalized API Client “Product”ViewModel… /products/1 mauroservienti
  11. 11. that’s a cache mauroservienti
  12. 12. Oh…and by the way… mauroservienti
  13. 13. Whatchoo talkin' 'bout, Willis? a report not a cache mauroservienti
  14. 14. 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
  15. 15. ViewModel Composition /products/ Catalog Sales Shipping Warehouse Client PKPKPKPK 1 ViewModel… mauroservienti
  16. 16. 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
  17. 17. Request Handling mauroservienti class ProductDetailsGetHandler : IHandleRequests { }
  18. 18. 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");
  19. 19. 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; }
  20. 20. 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
  21. 21. Output mauroservienti { "productName": "Banana Holder", "productDescription": "Outdoor travel cute banana protector storage box", "productPrice": 10, "productShippingOptions": "Express Delivery, Regular mail", "productInventory": 4, "productOutOfStock": false }
  22. 22. 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
  23. 23. is all what glitters gold? mauroservienti
  24. 24. What about grids? mauroservienti
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. Request Handling mauroservienti class AvailableProductsGetHandler : IHandleRequests { }
  30. 30. 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(); }
  31. 31. 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();
  32. 32. Event Subscribers class AvailableProductsLoadedSubscriber : ISubscribeToCompositionEvents { } mauroservienti
  33. 33. Composition class AvailableProductsLoadedSubscriber : ISubscribeToCompositionEvents { } public void Subscribe(IPublishCompositionEvents publisher) { publisher.Subscribe<AvailableProductsLoaded>(async (requestId, pageViewModel, @event, routeData, request) => { }); } mauroservienti
  34. 34. 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
  35. 35. 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", "…": "…", }] }
  36. 36. 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
  37. 37. ViewModel Composition Catalog Sales Shipping Warehouse composition gateway HTTP Request -> /products/1 Catalog Handler Sales Handler Shipping Handler Warehouse HandlerHTTP Response -> ViewModel mauroservienti Request matching Composition Composed ViewModel Creates empty ViewModel
  38. 38. 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
  39. 39. Vertical ownership Catalog Sales Shipping Warehouse mauroservienti
  40. 40. 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 cache10 minutes cache User Interface mauroservienti
  41. 41. 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
  42. 42. 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
  43. 43. Branding mauroservienti
  44. 44. 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
  45. 45. <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
  46. 46. magic UI Composition mauroservienti
  47. 47. 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
  48. 48. <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
  49. 49. <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
  50. 50. 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
  51. 51. 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
  52. 52. 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
  53. 53. 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
  54. 54. 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
  55. 55. 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
  56. 56. • 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
  57. 57. 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
  58. 58. Mauro Servienti Solution Architect @ Particular Software the makers of NServiceBus mauro.servienti@particular.net @mauroservienti //github.com/mauroservienti //milestone.topics.it mauroservienti
  59. 59. Thank you! Join me at the Particular booth Demos: bit.ly/dnd19-composition Videos: go.particular.net/dnd19-composition mauroservienti

×