DEV Community

Discussion on: Taking Marko's Tags API for a Test Drive

Collapse
 
ryansolid profile image
Ryan Carniato

Admittedly the store example is probably where this is the most involved. But I wonder how far it goes beyond this in Marko apps. This is a simple form. And these sort of pieces are probably the extent of state management in most MPAs.

Still as you may have noted I'm big on aggressive co-location. Mostly looking at if we can bridge the gap between the small and the largest apps better. You've probably noticed this trend in refactoring or software scaling. But where we start on the small side is closer to where we end up on the enormous side than all the steps in between. In Marko's case I think this article better captures our goals that the release announcement for the preview, and especially look at the code example at the bottom and the refactoring story.

I like that example a lot because it mirrors the pattern that I constantly see as we scale things. Every framework would have benefitted going from version 1 directly to version 3 if we knew we were going to version 3 ultimately, but introducing boundaries prematurely is not great. Only Marko in that example seamlessly goes through the 3 versions as a natural progression rather than a bunch of rewriting and refactoring.

When we are small we have everything in a single container. It's easier to see it all in front of us. The complexity is not at a point that this is hard to manage. As we grow this structure it becomes harder and we break it apart. Our first instinct is to group by like things. Whether that is as far as separating out state management from view, or simply reorganizing our component code to make it easier to add more features. Eventually though it scales beyond our ability to scale people or process. And we need to slice things the opposite way. At 90 degrees so to speak. More often this involves undoing what we did in the first step. And funny enough it often looks like having more of the initial thing rather than the intermediate step.

Now to be fair we go through this cycle over and over again. As now each of those pieces will do the same alignment over time and eventual split. That cycle I think is what a lot of the work is looking to address.

Now in my opinion this is all about the old game of hot of potato so to speak of who is left holding the state. When we moved to the client side we brough MVC with us, which was terrible because it didn't account for local state. This is what killed Angular1 etc, and lead to the rise of React. React wasn't the first one though to understand this. The whole MVVM thing was poking the holes in MVC. And Angular and Ember were trying to add a bunch of stuff to MVC to account for the fact they were missing something so vital in their model. It was the complication they added to an architecture that didn't want it that sunk those V1 versions. People probably could have kept using the awkward pattern as the fix was worse than the problem in a sense, but we moved on.

Now what we've been seeing recently is the continuation of that. Global State management has been a source of a lot of friction for frontend development. Well it is a bit more complicated than that. It's more that pulling everything local obviously doesn't exactly work in a naive sense. More boundaries have a cost as we scale. But since frameworks control the change mechanism instead of some externalized controller we end up playing a bit of an awkward game as we try to move certain things out of the components back into something shared. And this is basically where I've felt this whole "UI as an Afterthought" started gaining steam again.

But I just see 2 extremes with misalignment. Client frameworks don't really want global state, but are somewhat doomed to be inefficient without it. And classic server side mentality doesn't want local state. They want to be stateless. So who the hell is left holding the bag. That is why GraphQL has been a big deal, and things like ReactQuery in React ecosystem. Frameworks want to treat all state as local, and backend want to treat all things as stateless, the solution it feels is not to try to force either side to do what it doesn't want to. Just to abstract out the adapter. There is complexity here but there is a surprising amount of alignment.

Now, where does business logic live is the resulting question, because I'm basically advocating for smart backends, smart UIs, and dumb stores, (instead of the proposed smart stores and dumb UIs). The answer is mostly on the backend. Instead of viewing our architectures as 2 distinct MV___ that feed into each other I think we need something else. Classic server mechanics have us go from smart(business layer) to dumb(presentation layer) as we serve out our content. Classic app mechanics have us go from dumb (database) to smart (interactive UIs). So to me this about connecting and wrapping up those dumb parts. Ironically I call them dumb but they consist of some of the hardest problems like caching.

So what does this have to do with anything? I think we need to set ourselves up be able to best slice things the other way. And the way we do that is collapse the client/server model. It's not to say the results end up being that different than UI as an Afterthought but in a world where we offload the "global" state logic outside of our whole frontend mindset leaves the frameworks and UI tools the flexibility to optimize for their usage. Where aggressive co-location is a good thing.

TodoMVC example is fundamentally at odds because our model a client side one. Whereas ideally the store would look more like your refactor except we wouldn't need the separate JS file.

Collapse
 
peerreynders profile image
peerreynders

Still as you may have noted I'm big on aggressive co-location.

I understand the motivation of facilitating local reasoning through co-location. But explicit separation makes the need to keep things where they need to be more obvious. And I'm concerned that co-location would lead to coupling that even tooling may have a hard time to pry apart.

I like that example a lot because it mirrors the pattern that I constantly see as we scale things.

Which is why I took your "chart container" example and applied my thinking to arrive at rendezvous (live on glitch).

Now, where does business logic live is the resulting question, because I'm basically advocating for smart backends, smart UIs, and dumb stores, (instead of the proposed smart stores and dumb UIs).

Maybe you understand something differently by the term "Smart UI" - to me "Smart UI" means this:

The Smart UI “Anti-Pattern”

… That sums up the widely accepted Layered Architecture pattern for object applications. But this separation of UI, application, and domain is so often attempted and so seldom accomplished that its negation deserves a discussion in its own right.

Many software projects do take and should continue to take a much less sophisticated design approach that I call the Smart UI. But Smart UI is an alternate, mutually exclusive fork in the road, incompatible with the approach of domain-driven design. If that road is taken, most of what is in this book is not applicable. My interest is in the situations where the Smart UI does not apply, which is why I call it, with tongue in cheek, an “anti-pattern.” Discussing it here provides a useful contrast and will help clarify the circumstances that justify the more difficult path taken in the rest of the book.

❊ ❊ ❊

A project needs to deliver simple functionality, dominated by data entry and display, with few business rules. Staff is not composed of advanced object modelers.

If an unsophisticated team with a simple project decides to try a Model-Driven Design with Layered Architecture, it will face a difficult learning curve. Team members will have to master complex new technologies and stumble through the process of learning object modeling (which is challenging, even with the help of this book!). The overhead of managing infrastructure and layers makes very simple tasks take longer. Simple projects come with short time lines and modest expectations. Long before the team completes the assigned task, much less demonstrates the exciting possibilities of its approach, the project will have been canceled.

Even if the team is given more time, the team members are likely to fail to master the techniques without expert help. And in the end, if they do surmount these challenges, they will have produced a simple system. Rich capabilities were never requested.

A more experienced team would not face the same trade-offs. Seasoned developers could flatten the learning curve and compress the time needed to manage the layers. Domain-driven design pays off best for ambitious projects, and it does require strong skills. Not all projects are ambitious. Not all project teams can muster those skills.

Therefore, when circumstances warrant:

Put all the business logic into the user interface. Chop the application into small functions and implement them as separate user interfaces, embedding the business rules into them. Use a relational database as a shared repository of the data. Use the most automated UI building and visual programming tools available.

Heresy! The gospel (as advocated everywhere, including elsewhere in this book) is that domain and UI should be separate. In fact, it is difficult to apply any of the methods discussed later in this book without that separation, and so this Smart UI can be considered an “anti-pattern” in the context of domain-driven design. Yet it is a legitimate pattern in some other contexts. In truth, there are advantages to the Smart UI, and there are situations where it works best—which partially accounts for why it is so common. Considering it here helps us understand why we need to separate application from domain and, importantly, when we might not want to.

Advantages

  • Productivity is high and immediate for simple applications.
  • Less capable developers can work this way with little training.
  • Even deficiencies in requirements analysis can be overcome by releasing a prototype to users and then quickly changing the product to fit their requests.
  • Applications are decoupled from each other, so that delivery schedules of small modules can be planned relatively accurately.
  • Expanding the system with additional, simple behavior can be easy.
  • Relational databases work well and provide integration at the data level.
  • 4GL tools work well.
  • When applications are handed off, maintenance programmers will be able to quickly redo portions they can’t figure out, because the effects of the changes should be localized to each particular UI.

Disadvantages

  • Integration of applications is difficult except through the database.
  • There is no reuse of behavior and no abstraction of the business problem. Business rules have to be duplicated in each operation to which they apply.
  • Rapid prototyping and iteration reach a natural limit because the lack of abstraction limits refactoring options.
  • Complexity buries you quickly, so the growth path is strictly toward additional simple applications. There is no graceful path to richer behavior.

If this pattern is applied consciously, a team can avoid taking on a great deal of overhead required by other approaches. It is a common mistake to undertake a sophisticated design approach that the team isn’t committed to carrying all the way through. Another common, costly mistake is to build a complex infrastructure and use industrial strength tools for a project that doesn’t need them.

Most flexible languages (such as Java) are overkill for these applications and will cost dearly. A 4GL-style tool is the way to go.

Remember, one of the consequences of this pattern is that you can’t migrate to another design approach except by replacing entire applications. Just using a general-purpose language such as Java won’t really put you in a position to later abandon the Smart UI, so if you’ve chosen that path, you should choose development tools geared to it. Don’t bother hedging your bet. Just using a flexible language doesn’t create a flexible system, but it may well produce an expensive one.

By the same token, a team committed to a Model-Driven Design needs to design that way from the outset. Of course, even experienced project teams with big ambitions have to start with simple functionality and work their way up through successive iterations. But those first tentative steps will be Model-Driven with an isolated domain layer, or the project will most likely be stuck with a Smart UI.

❊ ❊ ❊

The Smart UI is discussed only to clarify why and when a pattern such as Layered Architecture is needed in order to isolate a domain layer.
There are other solutions in between Smart UI and Layered Architecture. For example, Fowler (2003) describes the Transaction Script, which separates UI from application but does not provide for an object model. The bottom line is this: If the architecture isolates the domain-related code in a way that allows a cohesive domain design loosely coupled to the rest of the system, then that architecture can probably support domain-driven design.

Other development styles have their place, but you must accept varying limits on complexity and flexibility. Failing to decouple the domain design can really be disastrous in certain settings. If you have a complex application and are committing to Model-Driven Design, bite the bullet, get the necessary experts, and avoid the Smart UI.

Evans, Eric. "Domain Driven Design: Tackling Complexity in the Heart of Software"; Four: Isolating the Domain, pp.76-79, 2003.


Perhaps now you understand my apprehension with "Smart UIs" — the implication here is that the approach does not scale.

Where aggressive co-location is a good thing.

The thing is I think with rendezvous having the bare Solid reactive system wrap the core application and binding it directly into the DOM on mount would be useful.

FYI: Congratulations on having Rich Harris to mention Marko in his Transitional Apps talk!
(Then again you're always talking about Svelte so it's only fair.)

Collapse
 
peerreynders profile image
peerreynders

Thank You for your response!

I've revisited

from 2020-Nov-23 as that seems to be the point where you got the most public feedback. Some questions:

<h1>${input.headerText}</h1>
<div/el />
<mount=() => {
  const chart = new window.Chart(el, input.data);
  return () => chart.release();
}/>
Enter fullscreen mode Exit fullscreen mode

Given that Chart uses a native DOM reference is it safe to assume that it is purely a client-side widget?

Consequently what actual HTML is sent from the server to the client? Something like:

<h1>Chart Header</h1>
<div></div>
Enter fullscreen mode Exit fullscreen mode

Side note: Good thing eBay doesn't plan on adopting Tailwind CSS.

Thread Thread
 
ryansolid profile image
Ryan Carniato

Yeah <mount> was an early version of our <lifecycle> tag. So this is all client side. So the server HTML would be what you wrote and when hydrated in the browser the chart would be created in this example. The refactor story equally applies to nested state, but nested side effects(browser only) I felt were the most obvious for this example.