DEV Community

Discussion on: JavaScript vs JavaScript. Fight!

Collapse
 
peerreynders profile image
peerreynders

What are we diffing - data, VDOM abstraction

Doesn't it make more sense to diff application state? And then only derive and update those parts of the view that need updating.

Granted diffing the DOM is more abstract and could perhaps be more reusable across a larger range of applications but it's like preferring to catch the horse after it has left the barn rather than just locking the barn door. Maybe it's more to the point that VDOM-based solutions are more focused on providing an alternative to the DOM API that is as far removed from the DOM API as possible.


don't assume the word component means the same thing to everyone.

Good luck with that one.

Certain members of the front end developer community seem to have a hard time differentiating between a framework in general and an "application framework". There seems to be a "one word", "(my personal) one meaning" mentality out there.

The first is an interopt story, the second an organization story.

I think the second one is even more problematic though - organization of what?

If you can get people to accept Web components as mere custom elements (while the existence of the shadow DOM suggests the DOM component perspective) I think people will quickly lose interest - especially as Safari only supports autonomous custom elements without a polyfill (or ponyfill).

But as soon as you take the solution farther, like SSR you are inventing new territory.

I think not foreseeing the need for direct support of SSR/partial hydration may turn out to be the Achilles heel of the v1 spec. Hand designed solutions (that take over browser parsed DOM fragments) are possible with the technologies introduced by the spec but are not likely going to be embraced by a community with a very specific expectation of what a "component" should be.

but the way we organize our applications is not.

… or components for that matter.

What I'm alluding to is that components boundaries need a lot more attention. You've published a number of articles about the performance impact of component boundaries but there is another aspect that I think needs more scrutiny.

On a legacy automotive engine a carburetor can be considered a "component". The way many front end framework components are authored is reminiscent of a small carburetor permanently joined to a slice of an engine block containing a single cylinder. When it comes to combustion engine design that idea would be considered ludicrous, yet in front end development this type of "self-containment" with high internal coupling is considered necessary to maximize productivity.

The Elm community gave up on "components" because of two distinct aspects of The Elm Architecture:

  • View: Transforming (part of) the model to (part of) the view (markup/DOM)
  • Update: Using a message from (some part of) the view to transform (part of) the model.

While both aspects communicate via the Model there is an important insight - the boundary around the part of state affected by a message often didn't match up with the boundary around the parts of state needed to render the affected parts of the view - thus making it impossible to package "a bit of View" together with a "bit of Update" (and therefore bit of Model) into a "self-contained component".

Now granted SolidJS's fine-grained reactivity gets around this by be being - well - fine-grained enough to not have to worry about these courser grained boundaries. But …

Any solution to a problem changes the problem.

While framework components need to manage view state - shouldn't that be separate from client-side application state? This of course goes back to UI as an Afterthought which itself is based on earlier work. It seems to me that the current generation of "light weight" front end frameworks promote framework component boundaries that aren't necessarily ideal for maintainability - trading it off for perceived short-term productivity gain and perceived but usually unrealized reusability.


Great Article!

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Doesn't it make more sense to diff application state? And then only derive and update those parts of the view that need updating.

As I mentioned in my comment; doesn't it make even more sense to diff nothing, and instead react directly to the data changes?

Collapse
 
peerreynders profile image
peerreynders • Edited

Not all "data" is created equal.

UI as an Afterthought:

State is the root of all revenue.

In the article state refers to state handled by MobX. So

Client side application state is the root of all revenue.

  • Events affect client side application state (i.e. not necessarily view state directly).
  • View state only represents a restricted but relevant part of client side application state in some transformed way.
  • So view state only has to be updated after the relevant portions of client side application state change (and preferably only once all the cascading changes have settled).
Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I don't see the problem here. State changes -> you respond accordingly. If only a small part of your state changes, you could specifically respond to only those changes (and only update the UI where necessary, or not at all).

Thread Thread
 
peerreynders profile image
peerreynders

Because …

Initially, design your state, stores, processes as if you were building a CLI, not a web app.

… because …

first encode what interactions our customers will have with our systems. What are the processes. What is the information he will need? What is the information he will send? In other words, let’s start with modelling our problem domain.

The solutions to these problems are things we can code without reaching for a UI library.

Current generation UI frameworks have a tendency to encourage client side architectures where "the UI is your application" - it typically takes a significant amount of discipline and effort to persevere with a "the UI is not your application" client side architecture.

Jest has pretty much shown the downsides of the "React is your application" architectural style. The testing framework is bloated and slow because that is what happens when you need to "unit test" "Smart UI Components".

Extreme Programming Explained (p.117, 2000):

You should test things that might break. If code is so simple that it can't possibly break, and you measure that the code in question doesn't actually break in practice, then you shouldn't write a test for it...

This lead to the The Humble Dialog Box (more generally the Humble Object) back in 2002 - yet for some reason front end development 20 years later continues to ignore this insight:

  • Use a lean, "dumb UI" that is so simple that it couldn't possibly break - that way you don't have to microtest it.
  • Without a "UI" to test, microtests can get away with lean and mean testing tools like uvu. Fast running microtest are more likely to be run frequently to generate feedback ASAP.
  • The UI is still tested but that can now become part of the heavier, less frequently run integration tests.

Elm's update/view separation has a counterpart in the OO world - Command Query Separation. Traditional OO would argue the accessors and mutators belong on the same object because that object manages a single concern. Command Query Separation challenges that notion; it argues that updating state (via commands) and retrieving a representation of state (via queries) are separate concerns.

Similarly a dumb UI fragment is simply a representation (query result via subscription) of some part of the application state. That fragment may contain some simple actions (commands) to initiate a change of some part of application state - perhaps even a portion distinct from the part the original representation was based on.

jQuery projects typically ended up being a Big Ball of Mud because as a library jQuery made it extremely easy to work with the DOM, leading developers to try to solve all problems within the DOM. But the DOM was simply the representation of server application state as parsed by the browser - if you were doing anything interactive on the client side that representation might no be enough to rehydrate client side application state (that is what Embedding data in HTML is for). Client-side application state needed to be managed separately from the DOM and it's changes needed to be propagated to the DOM. Segregated DOM demonstrated that approach but by that point AngularJS and React already had all the attention.

shift as much logic as possible out of the UI.

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Isn't that exactly what I'm proposing though? Having the data live in one place, and an mutation observer sitting on top of it changing the UI to reflect data changes. The data doesn't need to know about the UI and can live in its own little (testable) world.

User actions could then be passed back to the data as events, and suddenly the whole thing starts looking a lot like an MVC design. Or, if we want to look at it from the object side, we'd end up with two objects that communicate through message-passing, which is also an excellent way of de-coupling different parts of an application.

Thread Thread
 
peerreynders profile image
peerreynders

Having the data live in one place, and an mutation observer sitting on top of it changing the UI to reflect data changes.

My interpretation of your statement is that you are proposing a variant of MVC where the mutation observer is intimately familiar with both the model and the view - i.e. is tightly coupled to both. The logic around such a mutation observer would be anything but so simple that it can't possibly break.

The data doesn't need to know about the UI and can live in its own little (testable) world.

The point is to create the conditions where you don't need to microtest the dumb UI at all - delaying that part for integration testing.

and suddenly the whole thing starts looking a lot like an MVC design.

Even Pattern-Oriented Software Architecture Vol.1 (1996) lists some hefty liabilities for MVC (p.142), in particular:

Intimate connection between view and controller. Controller and view are separate but closely-related components, which hinders their individual reuse. It is unlikely that a view would be used without its controller, or vice-versa, with the exception of read-only views that share a controller that ignores all input.

A consequence of the tight view/controller coupling is that with the controller being the most complex piece of the pattern a "dumb view/controller" becomes essentially impossible.

Close coupling of views and controllers to a model. … You can address this problem by applying the Command Processor pattern … some other means of indirection.

(p.136)

apply the Command Processor design pattern. The model takes the role of the supplier of the Command Processor pattern, the command classes and the command processor component are additional components between controller and model. The MVC controller has the role of controller in Command Processor.

This is already is starting to look more like something like Flux where the "commands" become events or actions. The problem with the controller is that it orchestrates - orchestration logic tends to be complex. Sometimes choreography lets you break up that complexity into smaller more manageable pieces - ultimately that's what a state that accepts actions and supports change subscriptions is all about. That way an application can be built in terms of application actions and subscriptions to application events.

However in some cases the flux-style single global store just isn't optimal.

Aside: MVC: misunderstood for 37 years

So the ideal scenario is where "dumb UI fragments" can be updated via application event subscription (thin events requiring additional querying of the application to obtain relevant view state or fat events containing the relevant view state) and affect the application via application actions. The application gets to dictate the shape of the application events and application actions. The dumb UI depends on the application - not the other way around nor is there something (complex) sitting in the middle, brokering everything.

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

That MVC thing was a bit of an accidental red herring. Personally, I don't really like the idea of using MVC as a design philosophy on its own and only wanted to mention the similarity. More than the MVC part, I'd focus on the message-passing aspect of it, which to my knowledge is about the most decoupled way we know for two parts of a software system to interact.

Let's skip the controller part entirely for now, and just consider the data (model) and the UI (view): Ideally both would send very generic messages to the other, so neither would have to make any assumptions about the other.
But even in a more "real world" example, it will more often be the UI that will depend on certain assumptions about the data, not the other way around, which I find ideal.

This way, if we put the business logic on the data side (aka. coupling model and controller), we can reason about that side and test it independently of the UI.

The similarities to MVC are obviously still there, but more in the sense that it's a way to categorise components of our application, not a template for how to build it in the first place.

If any single paradigm could be used to describe this form of application structure, it'd be the original idea of Object Orientation (one that's more about message-passing and less about classes and inheritance).

Thread Thread
 
peerreynders profile image
peerreynders

(one that's more about message-passing and less about classes and inheritance).

That's not the mainstream perspective but the Joe Armstrong perspective:

… but Erlang might be the only object oriented language because the 3 tenets of object oriented programming are that it's based on message passing, that you have isolation between objects and have polymorphism.

And loitering here a bit subscribe-notify is a common communication pattern between BEAM processes as it decouples the Provider from the Subscriber. That is the relationship that you want between the application (Provider) and the dumb UI (Subscriber) in the context of UI updates. The beauty of that approach is that update logic doesn't have to know which parts of the UI are active to push updates out but only has to supply updates to the parts of the UI that are currently subscribed.

Aside: You may find David Khourshid's XActor interesting (and visit his Redux is half of a pattern articles while you're at it).

Ideally both would send very generic messages to the other, so neither would have to make any assumptions about the other.

Not sure about the "generic message" and "no assumptions" aspects:

  • "the application" needs some very specific information from the messages that it receives in order to act on them.
  • "the application" will likely publish messages of a very specific nature as unnecessary flexibility/configurability will lead to non-essential complexity.

"The application" leads, the "dumb UI" has to follow.

That said there is also a Consumer Driven contract dynamic between the consumer "dumb UI" and the provider "application". The UI has to fulfil its intended purpose so the application has to make accommodations:

  • The "dumb UI" depends on the application's contract
  • But the application contract is aligned with the needs of the consuming "dumb UI"

The aim isn't "loose coupling" but an application core that has no direct dependencies on the UI.

Nicolai M. Josuttis: p.9 SOA in Practice

Loose coupling is the concept of reducing system dependencies. Because business processes are distributed over multiple backends, it is important to minimize the effects of modifications and failures. Otherwise, modifications become too risky, and system failures might break the overall system landscape. Note, however, that there is a price for loose coupling: complexity. Loosely coupled distributed systems are harder to develop, maintain, and debug."

i.e. we're investing in just enough complexity to keep the application decoupled from the UI - but no more.

but more in the sense that it's a way to categorise components of our application,

There is that word again - component.

A physical, replaceable part of a system that packages implementation and conforms to and provides the realization of a set of interfaces. A component represents a physical piece of implementation of a system, including software code (source, binary or executable) or equivalents such as scripts or command files.

At least that's what the UML modeling glossary states.

don't assume the word component means the same thing to everyone.

😁