7 ms·
Principle #2 always bothers me. "Model the data part of the entities of your application using generic data structures (mostly maps and arrays)." and example
by piokoch 6y ago
Principle #2 always bothers me.
"Model the data part of the entities of your application using generic data structures (mostly maps and arrays)."
and example
function createAuthorData(firstName, lastName, books) {
return {firstName: firstName, lastName: lastName, books: books};
}
For a simple, obvious object like "person" it might work, but for a complicated domain object with many other composed objects this starts to be a pain.
I am not feeling ready to memorize all the field names, I prefer to have an object with documentation for each field.
- odshoifsdhfs 6y agoIf you use simple data structures without logic, it is basically what he is saying. Basically `createAuthorData` is a data structure constructor. There are some advantages by using _.pick or something in JS land, but these don't work for other more strictly typed languages. Also, I would be careful with using it so much as his example: ``` function createAuthorData(firstName, lastName, books) { return {firstName: firstName, lastName: lastName, books: books}; } function fullName(data) { return data.firstName + " " + data.lastName; } function createArtistData(firstName, lastName, genre) { return {firstName: firstName, lastName: lastName, genre: genre}; } ``` you can also do `fullName({firstName: 'Elmond', not:'really'})` and have an hard to catch bug (where an actual typesystem would catch it). You can either use a Protocol for this (PersonName) or a base class (though those are going out of fashion nowadays)
- pjmlp 6y agoOr when structure typing is available, type example = { firstName : string; lastName : string};; let name x = x.firstName;; name {firstName = "Joe"; lastName = "User"};; Or to make it more specific let name { firstName : string} = firstName;;
- andi999 6y agoUsually a map can be fine, but isnt it a maintenance nightmare. At least if you change an object, the compiler will complain if an attribute is not found, but this leads to runtime error/ or bad behaviour
- acarabott 6y agoAdding a version field can be useful to avoid this. Particularly if your entire state is stored in one object.
- andi999 6y agoAgree. But then every function using the map has to query the versiom field?
- hakre 6y agoWho says there is only one version field?
- hakre 6y agoYes, personally I'm (generally) against version fields, however in the OPs meaning as I read it, if you add the version field it breaks the value (the version field invalidates value comparison for equality) and therefore will end up adding complexity. This may go contrary towards the topic, as OP clearly states major goal is to reduce complexity. So adding a value and make it (inherently) incompatible in the value system breaks the benefits of a couple of the six points outlined in the OP (given the version field suggestion). Just saying. Your mileage may vary. But again, introducing version attributes is most of the time (and that is a warning) _increasing_ complexity. One of the articles referred to by the op is [out-of-the-tar-pit] which is fundamentally about complexity and WTF it is paradigms, on syntax level and language support. A version field is a counter on higher level on top of anything of it (and therefore in the off-topic domain already to a larger extend) and also ruining any of the value comparison ability (adding the version field exploits the value inequality in DO as per OP making it part of the versioning system) introducing meta-date and IMHO ruining DO. If you need to encapsulate state to take a short-cut, introduce state. Don't ruin value(s). Just my 2 cents. (/edit: better than version attributes are just any attributes as they work towards both directions of change. not that straight forward to deal with at first, but offering more flexibility. it would be that some older value handling functions are incomplete [but compatible] and new ones just fitting. similar, namespacing for attributes are orthogonal as well [as in closure, depends language at task supports them] while version fields are imposing one general forward direction only, IMHO only for last resort if any other kind of consistency is already considered lost beyond recognition [most often this is _not_ true in computer systems, it's just that everyone involved is too f-c-k'ing lazy to take care and then blames others for anything but getting the job done my/your-self. when in doubt ask operations, they may tell you if they have time]) [out-of-the-tar-pit]: https://raw.githubusercontent.com/papers-we-love/papers-we-love/master/design/out-of-the-tar-pit.pdf https://raw.githubusercontent.com/papers-we-love/papers-we-l... Moseley/Marks 2006
- weavejester 6y ago> I am not feeling ready to memorize all the field names, I prefer to have an object with documentation for each field. Using data doesn't preclude documenting fields, particularly if those fields are assigned a unique name. For example, in Clojure you'd namespace the keys, so instead of "lastName" one might write :author/last-name instead. This keyword can then be documented or even assigned a type/spec. (That said, you can't currently assign a docstring to a keyword in Clojure without the use of a third party library, or using a comment or external document. Hopefully a future version of Clojure will make this part of the core language.)
- hakre 6y agoThe author argues DO is in favour to reduce complexity, but also notes on the prices of it and reminds that it's in the eye of the beholder to decide whether or not it is of benefit. Many of the early PHP applications were written in a style using generic data structures (array that is in PHP) having functions dealing with all these. There is no free lunch. If you already have domains you can model in, the strategy to reduce complexity is perhaps more from the DDD book which looks like a higher level concept to me and therefore may be more fitting for higher complexity levels. (but again, one for sure can shoot in her own foot with DDD as well) What I like about the DO thing is it maps well on simple REST APIs sending and receiving JSON text. Quite popular these days to say the least if not mentioning Serverless. A similar trend can also be seen in structural logging. These systems are often distributed and complex. As it bothers you in your case, I would tend to say, it's better to stick to a domain if there is one. Across boundaries of domains, values to emit and receive data can work out very well though I can imagine. As so often, it depends.
- mumblemumble 6y ago> for a complicated domain object with many other composed objects this starts to be a pain It may be that complicated domain objects with many other composed objects are not compatible with this style of programming. Whether or not that's a good thing could be the subject of an interesting discussion. For one thing, negative experiences with the "large complex classes" approach to domain modeling is a major factor in the backlash against object-oriented programming. Lately I've been trying to familiarize myself more with the early literature of OOP, and I'm discovering that even OOP's early pioneers had already had bad experiences with it, and would warn against the temptation to do things that way. OTOH, there's undeniably a certain attractiveness to it, otherwise it wouldn't be so common.
- jayd16 6y agoYou can still make helper methods that get the field data for a person from the Person class if you really want. Its not the end of the world but you lose the benefits when you're operating on a single large object in that style. The reason you would use this setup is so you have all your data in contiguous arrays you can SIMD through quickly. If you're doing a lot of operations where n is low then its not that useful (and even detrimental) to orient your data array-wise.
- ickyforce 6y agoWhat bothers me more is the lack of a way to ensure that the data is valid. Like always things look great in simple examples where it doesn't matter but fail when it gets tricky. The main benefit of dedicated, encapsulated types is that they can preserve their invariants. Without that each function operating on "data" has to check that those invariants are kept. It's fine in trivial cases e.g. where this validation boils down to checking if a value is present or not - which might even be supported by the language. But I don't see this working in more complex cases. In my experience many (most?) bugs are caused by the implementation assuming something incorrect about the shape of the data and I feel like this principle only makes it more likely to happen.
- filoeleven 6y agoThis is alluded to in Price #2: No Packaging. I’d argue that grouping functions into namespaces largely resolves this problem. These two slides in the Clojure, Made Simple talk are my favorite counterpoint to even shallow objects being easier to work with than data. See starting at 50:00: https://youtu.be/VSdnJDO-xdg https://youtu.be/VSdnJDO-xdg If you don’t want to click through, the list of HttpServletRequest methods is bigger than the map itself! Each one is its own little DSL, and they are not consistent with each other. To do anything with the data, you first have to figure out which method gets the bit you want out of the thing. And that’s a simple case—the complexity only goes up from there. The map OTOH is just a map; you have hundreds of simple, composable functions that you can use to slice and dice the data however you need. Want the keys? (keys request) Want the keys of the headers? (keys (:headers request)) The user-agent value? (get-in request [:headers “user-agent”]) None of these are specific to a servlet request; they will work on any piece of data. And it’s trivial and transparent to build more specific functions out of them that fit your problem domain.