One repeated theme in this article is that `currentUser.id = 42` is bad and `currentUser.setId(42)` is good because it helps us organize our code (here are some quotes FTA[1]). Then the article builds up some complicated metaprogramming thing to turn the former into the latter. (Poe's law?)
BUT! Compare to the React.js way of doing things. React.js encourages us to store our models in plain old javascript data, e.g. nested JSON-y blobs, not encapsulated by classes and methods, you don't have getters, and setters have major limitations. Yet we can still separate concerns, re-rendering can still be organized to the right place, we can still log updates and cleanly validate. Without subclassing, or writing change callbacks, or resorting to metaprogramming. (Hasn't J2EE and Rails taught us that metaprogramming leads to really complicated systems? Also React.js is just an example, there are other systems where we access data directly)
I don't think it's direct property access that's the problem, but rather, mutability.
[1] "There was no way to decorate such operations with cross-cutting concerns like logging or validation"
[1] "Direct access does not allow you to organize the functionality associated with getting and setting properties, it forces the code doing the getting and setting to also be responsible for anything else associated with getting and setting."
[1] "Whereas we can’t do anything like that with direct property access. Mediating property access with methods is more flexible than directly accessing properties, and this allows us to organize our program and distribute responsibility properly."
The”React Way" is another way to organize code, and I have no problem with it. In fact I like it.
However, if we’re arguing about whether to mediate property accesses with methods vs. immutable data vs. true observables vs. whatever else, we’re talking about which of the .1% of the JavaScript code in the wild is the best way to move forward.
The point of this is to illustrate the difficulties of the other 99.9%. Including explaining why existing popular frameworks like Ember and libraries like Backbone do what they do.
I think the "React.js way" through a Flux flow takes this idea even farther. Now, instead of an individual object being allowed to change itself, we go through an even greater indirection, firing an abstract action and letting a store figure out how to make changes to the appropriate data.
The real question is: if our data has real validation and invariants, who maintains them?
- Does every piece of code that might change one attribute have to worry about it?
- Do individual objects protect themselves through methods and take responsibility?
- Or does a whole managed context take responsibility, allowing it to guarantee invariants about groups of objects together?
Experience tells us the first option quickly becomes problematic. The second certainly works, but requires discipline in how you design objects (the origin of many of the DDD patterns). The third is less widely used, seems to work better, but has some overhead. But don't kid yourself that it's less indirection/metaprogramming. It's more.
Great post, I'm gonna take this off topic now because I've been thinking a lot about this and curious your thought:
> we go through an even greater indirection, firing an abstract action and letting a store figure out how to make changes to the appropriate data
Can't this indirection just be modeled as a function? Instead of firing an action we call a function, which returns the appropriate state updates. No metaprogramming, and composable when used with cursor pattern to run the actual state update effect. (ActionStoreDispatcher flux doesn't compose)
(You probably know about this but others may not.) I like Elm's architecture, which has typed update functions that can be nested. This makes the full stack of the component composable and is something like a cursor: https://gist.github.com/evancz/2b2ba366cae1887fe621
I don't think React really takes too much of an opinion on how we store our models -- it is, at it's core, a view layer -- a function that takes as input a model and spits out a UI. I've written model layers behind React UIs that use different paradigms for manipulating the model -- they've all worked, and in hindsight, I wouldn't use one particular paradigm for everything -- there probably isn't one "right" paradigm for organizing the model, which is one of the biggest strengths of React as a framework.
One (officially blessed) example of a different model layer is combining React with something like Immutable.js, which does use setters which return new objects for each modification, allowing shallow comparisons of everything.
to be clear, Immutable.js "setters" are only there because we don't have native syntax for updates. The article is talking about using setter methods to abstract over data changes so we have a place to run code when things change. That's not what immutable.js setters are for.
There are many, many, MANY things you can do when you make setting and retrieving a property "programmable." Injecting "method advice" to propagate changes--as the post describes--is one. Making data immutable through copy-on-write is another.
Same mechanism, entirely different architectural objectives.
BUT! Compare to the React.js way of doing things. React.js encourages us to store our models in plain old javascript data, e.g. nested JSON-y blobs, not encapsulated by classes and methods, you don't have getters, and setters have major limitations. Yet we can still separate concerns, re-rendering can still be organized to the right place, we can still log updates and cleanly validate. Without subclassing, or writing change callbacks, or resorting to metaprogramming. (Hasn't J2EE and Rails taught us that metaprogramming leads to really complicated systems? Also React.js is just an example, there are other systems where we access data directly)
I don't think it's direct property access that's the problem, but rather, mutability.
[1] "There was no way to decorate such operations with cross-cutting concerns like logging or validation"
[1] "Direct access does not allow you to organize the functionality associated with getting and setting properties, it forces the code doing the getting and setting to also be responsible for anything else associated with getting and setting."
[1] "Whereas we can’t do anything like that with direct property access. Mediating property access with methods is more flexible than directly accessing properties, and this allows us to organize our program and distribute responsibility properly."