The HIDDEN Challenge of Microservices: UI Composition

แชร์
ฝัง
  • เผยแพร่เมื่อ 17 ม.ค. 2025

ความคิดเห็น • 77

  • @CodeOpinion
    @CodeOpinion  2 ปีที่แล้ว

    Are you using UI and/or ViewModel Composition? How do you tackle composing data from multiple services?

    • @andrewcalderwood6102
      @andrewcalderwood6102 2 ปีที่แล้ว +4

      We use Graphql With a gateway aggregation service. means the client just treats it like a single backend.

    • @renedekkers735
      @renedekkers735 2 ปีที่แล้ว +1

      I prefer the idea of a read model in a separate database with tables that contains data that is optimized for a specific view and is kept up to date with integration events. This will keep the queries very simple and fast

    • @anthonylevine8783
      @anthonylevine8783 2 ปีที่แล้ว +1

      ​@@andrewcalderwood6102 Been looking at this option for awhile. Have you run into any issues? And what stack are you using for the backend if you don't mind me asking?​

    • @dino56ac49
      @dino56ac49 2 ปีที่แล้ว +1

      I've added a NoSQL storage to my BFF that stores projections tailored for the BFF access patterns. It's integrated to the services through events.
      Thanks for the video! I've been looking at this problem and how it forces you to shape your APIs in a way that I don't find cohesive with my domain concepts

    • @Wouldntyouliketoknow2
      @Wouldntyouliketoknow2 ปีที่แล้ว

      How does gateway aggregation solve the issue of fetching a page of data with sorting and filtering accross the boundaries? I guess it has to pull back all data from all sources and do the join in memory which could be a massive waste of resources and make this approach just not viable for these particular circumstances? In the interest of being consistent and because you know this aggregation approach can't cater for all scenarios, it's better to solve the issue of how to materialise a view that is ready to be read from (I.e basically a cache as explained in this video) and that means something like event carried state transfer to do that cleanly. What I am saying is that once you have distributed data scenarios, there is a type of a requirement that can force your hand into this option and therefore its better to plan for how you will solve it when that requirement arises, and this begs the question as to whether you should do gateway aggregation at all because it can muddy the water - i.e its easier to reason about the system if you know that all views accross boundaries are backed by the same mechanism and subsystem. If some areas use gateway aggregation and some use cache there is perhaps always an extra step to remember which functionality maps to which mechanism. It's a tradeoff because gateway aggregation is technically a "live" view so in that sense is more what users and product owners expect often when putting forth user stories. However once you venture into distributed systems I feel certainly product owners should be comfortable with the idea of caches driving views of data and the concepts of eventual consistency.

  • @fr3fou
    @fr3fou 2 ปีที่แล้ว +13

    Wouldn't storing catalog-related data in the Sales service violate SRP? I kind of find it counter intuitive for the BFF to query the Sales service for rendering a catalog page to be honest. Imagine you also have to render the product stock, something which you'd most likely store in the Inventory service, would you also propagate changes from there to the Sales service? I think that this can get really complicated when you want to query things like "give me all paginated products, sorted by price, with brand X which are in stock" - not sure how you're supposed to compose such query

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +2

      Yes, it can get complicated, which is why this becomes an issue. Most often this composition for this type of listing that involves fitlering/sorting/pagination gets very limited. Often this can involve a service that is specifically for aggregating all this data into a central spot which owns that capability. You'll also notice that it becomes very limited in the sense you can just filter on everything. Go check out something like Amazon, you can't filter on everything under the sun. You can't change the pagination size. You're limited to the sorting options.

    • @johnporter8896
      @johnporter8896 2 ปีที่แล้ว +7

      If you have a frontend service that needs data from multiple other services then the best approach is a separate read model as a service that has already aggregated all the relevant data together. That way you have a totally denormalised view model that is queryable separately to the other services!

    • @alexweisberger1669
      @alexweisberger1669 2 ปีที่แล้ว +2

      That’s what he described here. The event carried state transfer is what keeps a read model in another service in sync with the data from the producer.

    • @bezik7124
      @bezik7124 2 ปีที่แล้ว +3

      @@alexweisberger1669 the difference is, the video shows that the sales service did that. What John described on the other hand, is completely another service, let's call it Product pagination service which aggregates all the data that you need to execute these filter / sorting, and is doing so by listening to events dispatched by product, sales and stock services.

  • @leoMC4384
    @leoMC4384 2 ปีที่แล้ว +1

    I watch your videos every once in a while. I don't understand most things, I'm an old self-taught developer looking for his first job, I'm 37. Your channel keeps me engaged, I wish I could understand everything some day. Keep it up. 👍

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      Keep at it! Thanks for watching

    • @iliyan-kulishev
      @iliyan-kulishev 2 ปีที่แล้ว

      Welcome to the club, buddy. 33 here, just started my first dev job. Learning so much from this channel. I also don't understand all things, but the general concepts and ideas become clear. I can discuss microservices fx with somebody that has years in that without actually having worked on that.

  • @xoca7171
    @xoca7171 2 ปีที่แล้ว +2

    How does the "transparent" approach solves the sorting problem?

    • @edwnmrtnz
      @edwnmrtnz 2 ปีที่แล้ว

      interested on this too.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      It doesn't. It's just an approach to use, assuming you don't have that issue to have ViewModel composition without having to write manual BFF code to do all the appropriate calls to each service.

  • @barrydev
    @barrydev 2 ปีที่แล้ว

    Excellent video! Love the content and concise format.
    One aspect of service-based architecture that's sometimes not given a lot of thought is the implicit architecture characteristic coupling between services when they're participating in a single request. i.e. when a user makes a request for the product listing view, it is a requirement that both Sales and Catalog are currently available. That guarantee, if not explicitly catered for via some coordinated infra and monitoring, can completely ruin the end user experience. A BFF with its own local cache would best suit a scenario where uptime cannot be guaranteed for all participating services - with the trade-off of stale data, as mentioned.

  • @andreipacurariu2013
    @andreipacurariu2013 ปีที่แล้ว

    Great video Derek, and a good summary of the composition patterns.
    On the topic of client-side composition, I would like to share a different technical approach which avoids the N+1 query pattern you correctly highlight. For this, it's useful to differentiate between "dumb" components which deal only with presentation and "smart" components which interact with backend APIs. In my experience, the best solution is to have the pages in the application (e.g. the page were you're displaying the grid and filters in your example) act as containers for "dumb" components such that only the pages are "smart" components. Thus, the pages implement the actual composition and fetch all the data while the "dumb" components (e.g. product image, product price, etc.) that make up the page are simply passed in the data and display it. In your example, you could have two different client-side services one for the Product Catalog Service and one for the Pricing Service and the page can invoke both of these in a sequence - first get the products that match the filter from the Product Catalog and then retrieve the appropriate prices for these from the Pricing Service passing in an array of product SKUs (a batch query). Thus, with only two sequential requests you can get all the necessary data for the page avoiding the select N + 1 problem. It's noteworthy also that if you need to get product data from other services (not just prices) these other requests could be performed in parallel with the pricing request since they're all based on the same list of SKUs. This is a client-side composition pattern that I've used with good results.

    • @stefan-d.grigorescu
      @stefan-d.grigorescu ปีที่แล้ว

      I think the issue about this is what Derek presented at the "List, pagination, sort, filter" section. In your exemple you made all these based on information contained within the first service, then just completed your products' info with the data queried in batch. However, fetching a page of 100 cheapest products that are also filtered by some data in the other service is still a problem. Note that loading all the products data from both services in the page components then doing all the filtering and sorting on FE side might get difficult as the total count of products increases

  • @arielmoraes9427
    @arielmoraes9427 2 ปีที่แล้ว +1

    Excelent video, in the past few weeks I've been working my mind around such problems and my conclusion is: business wanting views that provide the ability to sort, page and filter in a very dynamic way, are more aligned to some sort of analytical OLTP, so why not build an analytical service that could provide all the needed information and be built to provide that data in the best way possible? Furthermore that could be used to provide external APIs using GraphQL or OData and also be monetized depending on the business, thus providing easy analytical capabilities too.

  • @allinvanguard
    @allinvanguard 2 ปีที่แล้ว +1

    Genuine question, would you say there is also a "threshold" at which gravitating back towards on-demand synchronous RPC calls is the better fit? With data redundancy across services, it also sounds like it could spiral out of control fairly quickly since keeping data in multiple places also requires keeping it up to date, to some extent at least. I assume that this can also become messy with scale.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +3

      The listing/grid example is usually the most typical because it involves so many different services and you generally want filtering/sorting/paging. Instead of having all kinds of services propagating data all over the place (nightmare) you'll generally end up with a service that's entire purpose is for searching. But its still limited in functionality. As an example, go look at Amazon. You can't filter/sort/page by whatever you want.

    • @allinvanguard
      @allinvanguard 2 ปีที่แล้ว

      @@CodeOpinion Ah, I see, I didn't even get that far into the video yet. Thanks!

  • @egorsozonov7425
    @egorsozonov7425 2 ปีที่แล้ว +2

    So apparently the solution to microservice composition is... to make a monolith? Because that's what the third solution is. If Sales stores all the Catalog data and you don't need to consult Catalog to get its data, then you have a monolith again. And what if you had N microservices with potentially 2^N different kinds of requests, how would you handle the cache then? I think the BFF solution is the best, and it can handle the "sort by price" scenario as follows: 1) SELECT ProductId, Price FROM SALES ORDER BY Price ASC SKIP Pages*PageSize LIMIT PageSize 2) SELECT * FROM Catalog WHERE ProductId IN (...result of (1)...). There's just one request to each microservice, no N+1 queries, and we only get the data for one page at a time. And no monolith.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      As mentioned with event-carried state transfer, there are some serious pitfalls and I'd rather use events as notification for workflows and not data propagation. I've covered this in other videos. However, there really is no different then building out a reporting/BI solution where you would need all this relevant data aggregated. So as long as you are NOT using it for commands and business logic and expect to be using consistent data. The trouble with event-carried state transfer is people using it as a crutch because they think they need data within a particular boundary, when they really don't. Typically if they weren't using events, they'd be doing RPC calls, which is equally as coupling, but more so because it's temporal coupling as well.

  • @CarlosAlbertoBrasil
    @CarlosAlbertoBrasil 2 ปีที่แล้ว

    Thank's you solved one of my doubts about microservices

  • @AndreasRavnestad
    @AndreasRavnestad 2 ปีที่แล้ว +2

    6:49 - 7:50 Strong evidence that you are in the progress of building a distributed monolith. Don't do this.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      As I've replied in a bunch of comments, I'd prefer not to do data propagation via event-carried state transfer as well as it has a lot of implications. However, there are situations where I think it's entirely valid, such as query only purposes such s reporting. Ultimately a report is a query.

  • @FISS007
    @FISS007 2 ปีที่แล้ว

    For the sorting problem, why not do it the other way around ? The sales boundary stores prices and products ids, you query products ids and prices from the sales boundary, and then you query products infos from the catalog by the given IDs.
    In general, you query the ids from the boundary that owns the sorting/paging critera and then query the rest from the corresponding boundaries given the returned ids (since items ids are stored in every boundary).

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      Add another service into the mix. Say the product rating. Now you want to filter by product name (catalog), filter and sort by rating (review), and sort by price (sales).

  • @bartoszpiasecki8187
    @bartoszpiasecki8187 4 วันที่ผ่านมา

    Thank you for your great explanation way data from different microservices can be composed by BFF component and share to client. I was looking it. I would have a other question about duplication data cross different microservice. Let's assume simply scenario for ecommerce solution: Catalog microservice gather products with price. User adds selected product to basket (basket microservice). Should basket item contain price? If let's say in checkout process there is a need to verifiy user limit is exceeded or to sum order price. I imagine basket item contains guid reference to product from catalog bounded context but there is need to keep price too? Or maybe is enough in checkes process sync request (http request) should be done to get price from product context? Regards :)

    • @CodeOpinion
      @CodeOpinion  2 วันที่ผ่านมา

      Well let's say the price is $10, the user adds it to their basket, then you change it to $11. What should the price of the item be when they checkout? There isn't a right or wrong here it's just how you want it to behave. Then based on that you would decide where you want to keep the price.

  • @ramanam123
    @ramanam123 ปีที่แล้ว

    This is definitely helpful but what about the scenario where we already have data and want to use this approach going forward
    What are the strategies to kind of catch-up first and then have this approach being followed going forward

  • @nikolayivanov9984
    @nikolayivanov9984 2 ปีที่แล้ว

    Great one!
    I have the following scenario. Let's say we want to create a Product in service A, upon creation an event is emitted and sent to service B, where the logic decides what label it should put on the product. The logic for the label is in service B since that service handles a lot of other products and puts labels, that do not come from Service A, but from an external service.
    From a UI perspective, once we create a product, we want to view it immediately with the label.
    The current problem is that the message might not have been consumed fast enough and the data is missing from Service B which we use for reading.
    Are there patterns that handle these scenarios?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      So eventual consistency is the issue. Check out th-cam.com/video/wEUTMuRSZT0/w-d-xo.html

  • @camcommute
    @camcommute 2 ปีที่แล้ว

    What about having just one database? Each microservice has separate schema; some boundaries there. Then the BFF does the query across the scheme?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      Well you'd be integrating at the database. Not personally a fan as it's hard to change and evolve (version) as a change to the underlying schema involves the boundary owner of the data and the BFF. Versioning

  • @adambickford8720
    @adambickford8720 2 ปีที่แล้ว

    If stale data is a concern, version it. i.e. optimistic locking. Not a silver bullet but caches are pretty important for anything involving a user experience.

  • @Galakyllz
    @Galakyllz 2 ปีที่แล้ว

    Another great video. Thanks.

  • @Vangerdahast
    @Vangerdahast 2 ปีที่แล้ว

    Can't BFF gather all that information. So for all queries it'll use the data it stores, for commands it'll pas the request to the respective service?

  • @takyuchan7920
    @takyuchan7920 2 ปีที่แล้ว

    Nice video

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      Check out this video, might add more context: th-cam.com/video/anL-RGKz_Ak/w-d-xo.html

  • @LarsKemmann
    @LarsKemmann 2 ปีที่แล้ว

    I agree with @fr3fou, the "fat events" approach violates SRP which in turn makes the system harder to maintain. Wouldn't a better approach be to add a sort/page/filter endpoint on the Sales service, something like "Give me the next X product IDs when sorting by price", and then passing those IDs to the Catalog service via an endpoint that accepts a set of product IDs and returns their catalog information?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +4

      Yes, that's possible. The immediate question I would get suggesting that from someone would be then, well how do you also filter on the brand? That belongs to the catalog service. Now were starting at the catalog service, filtering there, going to the sales service and based on those ProductIDs sorting by the price. But you also want to filter on some other on what is free shipping, which belongs to another service. Now what? My point being is there isn't a right/wrong answer, it depends on your requirements. There are tradeoffs to everything. As mentioned in other comments, there are different solutions to this involve having a singular aggregated service that handles this instead of propagating data all over the place, which is a nightmare.

    • @LarsKemmann
      @LarsKemmann 2 ปีที่แล้ว +2

      @@CodeOpinion Yep, that makes sense. (TH-cam hadn't shown me your response to that comment yet when I posted this... speaking of stale data. 😂)

    • @anthonylevine8783
      @anthonylevine8783 2 ปีที่แล้ว +3

      Another point to add is that would you consider having a reporting database to violate the SRP rule? Of course not. This is the same concept. SRP has nothing to say about where/how you store your data, it's about what responsibilities you have (with that data).

    • @LarsKemmann
      @LarsKemmann 2 ปีที่แล้ว

      @@anthonylevine8783 You're right! I overstated that. It would have been better to say that, as a consequence of SRP, only one service should ever *own* a piece of data -- but there's nothing *inherently* wrong with sharing that data with other services *as long as* those services are simply consuming that data (and, if the services are distributed, assuming that we either have distributed transactions or we're okay with eventual consistency).

  • @brandonlange2260
    @brandonlange2260 2 ปีที่แล้ว +2

    Really interesting take on an underrated problem.
    I think a good addition to add to this is a possible requirement whereby you need to display current stock, therefore needing to include some kind of warehouse boundary to this all.
    Because stock obviously fluctuates far more frequently than price, causing what I think becomes a pretty interesting topic.
    (not to even mention specials) 😅

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +2

      Yes, or a product rating/review that is likely in another service.

  • @thedacian123
    @thedacian123 2 ปีที่แล้ว

    Does not ,gateway and bff patterns make the same stuff i mean model aggregation ,reverse proxy ,security etc?

  • @MrDomenic123
    @MrDomenic123 2 ปีที่แล้ว

    Thanks a lot for this content, Derek! Do you know if there is an "equivalent" service bus for Java?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      I'm not in the JVM/Java ecosystem, so I can't really recommend anything. From what I'm told, take a look at eventuate.io

  • @vupham-i2v
    @vupham-i2v ปีที่แล้ว

    7:56 so dose that mean I don't need a BFF anymore?

  • @RC-cy7pd
    @RC-cy7pd ปีที่แล้ว

    Where would GraphQL Federation fit in?

  • @BertrandLeRoy
    @BertrandLeRoy 2 ปีที่แล้ว

    Hi Derek. Excellent video as usual. The topic of view model composition really resonated with me because of my involvement with Orchard, which is largely built around this idea. Did you ever get a chance to check it out and if so, what are your thoughts?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      I'm familiar of Orchard but haven't really dug too deep in it. Any references on where to start?

  • @sorteslyngel2k
    @sorteslyngel2k 2 ปีที่แล้ว +1

    This is a good display of why microservices architecture should be avoided at all costs. There simply is no good solution to this problem... and it has a high effect on the end user of the system. This is why so many implementations end up having service-to-service communication.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      I think defining boundaries is incredibly important however the confusion lies in assuming that logical boundaries are physical boundaries. I don't think you should avoid defining logical boundaries. I think you should only define physical boundaries as logical ones if you actually need to.

  • @ArinSinabian
    @ArinSinabian 2 ปีที่แล้ว

    If we add a new service that combines data from both catalog service and from sales services via event carried state transfer I think is a better approach. I am thinking that we don't need "pollute" sales service with data from catalog service. I rather let sales be sales and have a new service called "product query service" that combines data from catalog service and sales services. I think there is a risk we add more fields to products from another service say "rating service". Maybe we want to be able to sort by rating then we need to pollute sales service with that data as well. In my opinion it would be better with a separate service and it would also have single responsibility to just query/page/sort products. What would be the downside of going doing this? Maybe both approaches have pros and cons?

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      Yes, moving data to an aggregated service is a similar approach you'd use specific to reporting. You need to compose all the data together to report on. Purely for query purposes.

  • @shahrazkl
    @shahrazkl 2 ปีที่แล้ว

    Amazing!

  • @PiesekLeszek90
    @PiesekLeszek90 2 ปีที่แล้ว

    So instead of having one database, we create multiple specialized databases.. that also save other, potentially stale data just for queries? This sounds like we'll end up with multiple copies of one big database but having to deal with stale data on top of that.
    Also, why limit ourselves in the order of requests or sorting only when fetching from a service? If we want to sort by price, query sales service with ordering first, then load catalog query into the prices. They both need to mach by sku anyways! But ok, you'll probably say "but pagination can be done only by catalog service" or that you want to sort by name or something, that is possible by simply merging the data differently, fetch the paginated products then fetch ordered prices using list of sku's, then merge products into prices, keeping the price ordering, falling back to product order if needed, done.
    Limits can be done same way, simply dropping already fetched records that don't match sku from other list. It's all a matter of ordering your requests/data merges and it's simpler than it sounds.
    I also want to mention, with modern SPA you can do what BFF would in some cases, having a wrapper component "product list" that does fetching of price and product then passing them down to child components is absolutely ok. Fetching prices one by one in a list just sounds like a bad code. I'm not saying BFF's are bad, it's just this example. BFF is great if you have a RESTful API or multiple apps that use the same services.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      I'm not suggesting event-carried state transfer to propagate data as a always solution, as I mentioned in the video as it has a lot of pitfalls. I mentioned it because it is popular and does have a use-case when the data your keeping for a local cache is non-volatile. There are situations it can be helpful. As for the sorting/paging/filtering. You want to sort by price and review rating. All three are from 3 different services. This is often the case for using event-carried state transfer and having a main aggregate that contains all the primary data you need for all this. Most often however it hast o be limited to some degree as you aren't going to support filtering/sorting on everything imaginable.

    • @anthonylevine8783
      @anthonylevine8783 2 ปีที่แล้ว +1

      "It's all a matter of ordering your requests/data merges and it's simpler than it sounds." isn't even remotely true. Let's say Service A contains a list of people (and their names) and Service B contains a list of all of the products they've bought (not every registered user has bought something btw). What if I want the top 10 most expensive products bought by people named "Bob"? How would you do that in your example without possibly needing to load every user record with the name of Bob?

  • @UdiDahan-jp4xy
    @UdiDahan-jp4xy ปีที่แล้ว

    Another form of composition is useful for advanced search scenarios: Engines.
    For more info on this approach, see my presentation on the topic:
    th-cam.com/video/Fuac__g928E/w-d-xo.html

  • @LotnyLotnik
    @LotnyLotnik 2 ปีที่แล้ว

    I don't think this will be a problem in Microservices service. Just add another microservice that will handle caching of both image, name and price. Then you UI will hit just one microservice! Easy

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว

      Yes, that's an option to have an aggregated service. "Easy", I wouldn't use.

  • @bobbycrosby9765
    @bobbycrosby9765 2 ปีที่แล้ว

    As far as fat events go...
    In the web world data is always stale, it's just the time horizon you're talking about. Even in a monolithic SSR webapp, the html you're sending across with a price is client side state that can become stale from the source of truth - the server. You already should be thinking about this stuff and deciding if its something you need to deal with or not, but sadly many people don't seem to.

    • @CodeOpinion
      @CodeOpinion  2 ปีที่แล้ว +1

      Agree. The moment you fetch data from a backing store, it's stale. Making business (logic) decisions based on that data however is the issue, as mentioned. Data consistency becomes a problem in that case.

  • @florianfanderl6674
    @florianfanderl6674 2 ปีที่แล้ว

    What do you think about CQRS? Building a separate application for this list page, that listens to changes from the product catalog and sales service and combines them into a new view in a separate database. I personally find UI composition the last thing I would do.

    • @florianfanderl6674
      @florianfanderl6674 2 ปีที่แล้ว

      Ah shit 😁 the answer is in the end of the video 😁👍I was too fast

    • @Kubkochan
      @Kubkochan 2 ปีที่แล้ว

      I don't think this is CQRS. CQRS IMHO should not be cross boundaries. This is more like creating report view from ecst.

    • @florianfanderl6674
      @florianfanderl6674 2 ปีที่แล้ว

      @@Kubkochan my definition of CQRS is, that there are different projections (views) of data based on a domain model. Usually either based on the events that you send cross the domain boundary or the events from eventsourcing. But I'm not sure if my definition is correct 😉

  • @nove1398
    @nove1398 2 ปีที่แล้ว +1

    It is really not covered enough

  • @proofit404
    @proofit404 2 ปีที่แล้ว

    Лучший!