Hacker News new | ask | show | jobs
by bob1029 1957 days ago
This is basically our approach with Blazor now. I am not sure Microsoft has even picked up on or documented our pattern yet, but we do something where:

1) Define a domain model + service(s) that fundamentally addresses the logical business functionality without any notion of a specific UI or its stateful nature. This service should be written in terms of a singleton and injected as such. This is something that should be able to be 100% unit testable, and could be exposed as a prototype in a console application or back a JSON API if necessary (i.e. pretend you are writing a SAAS).

2) Define UI state service(s) that take dependency on one or more domain services. These should be scoped around the logical UI activity that is being carried out, and no further. The goal is to minimize their extent because as we know more state to manage means more pain. These would be injected as Scoped dependencies (in our use of Server-Side Blazor), such that a unique instance is available per user session. Examples of these might be things like LoginService, UserStateService, ShoppingCartService, CheckoutService, etc. These services are not allowed to directly alter the domain model, and must pass through domain service methods on injected members. Keep in mind that DI is really powerful, so you can even have a hierarchy of these types if you want to better organize UI event propagation and handling.

3) Define the actual UI (razor components). These use the @inject statement to pull in the UI state services from 2 above. In each components' OnRender method, we subscribe to relevant update event(s) in the UI service(s) in order to know when to redraw the component (i.e. StateHasChanged). Typically, we find that components usually only inject one or 2 UI state services at a time. Needing to subscribe to many UI state events in a single component might be a sign you should create a new one to better manage that interaction.

This is our approach for isolating the domain services from the actual UI interactions and state around them. We find that with this approach, our UI is quite barren in terms of code. It really is pure HTML/CSS (with a tiny JS shim) and some minor interfacing to methods and properties on the UI state services. This is the closest realistic thing I've seen so far in terms of the fabled frontend/backend isolation principle. Most of the "nasty" happens in the middle tier above, but it is still well-organized and easy to test. By enforcing immutability on the domain services, we ensure discipline with how the UI state services must consume the model. Blazor then goes on to eliminate entire classes of ridiculous bullshit by not forcing us to produce and then consume an arbitrary wire protocol to get to our data.

2 comments

Very interesting - thanks for posting this.

> By enforcing immutability on the domain services, we ensure discipline with how the UI state services must consume the model

Could you expand on that part? Do you mean that your domain services use an append-only approach to manage state?

Not necessarily append-only, but making sure that when we get a copy of something from a method - i.e. GetUser(), that the UI or its state services cannot cause mutations to propagate back down into the store without first going through another explicit method like UpdateUserName().

We don't play around with event sourcing right now, but it might be feasible at and above the UI state services if we wanted to do things like replay a user interaction with our app. The only caveat is that our domain services are very very messy in terms of side effects, so the practical utility is bounded to isolated UI testing.

First, thanks for this response. It's super interesting and get's my mind spun up in a lot of directions it wasn't going before.

Second, with this type of modeling on the separation of GUIs and system, what type of movement do you think there will be in Microsoft and Google going towards an even more minimal computer, almost entirely reliant on the cloud space for compute power. Google's machines are practically there, but from what you've mentioned above seems like a more fully "realized" approach than Google's.