[HN Gopher] On Having a Data Object
___________________________________________________________________
On Having a Data Object
Author : Theaetetus
Score : 34 points
Date : 2025-10-27 11:52 UTC (5 days ago)
(HTM) web link (www.natemeyvis.com)
(TXT) w3m dump (www.natemeyvis.com)
| reillyse wrote:
| The fact that people expect a data object, as argued by the
| author, is a very strong argument in favor of having one.
|
| Onboarding new programmers to your codebase and making the
| codebase simpler for developers to reason about is a massive non-
| functional benefit. Unless you have a very strong reason to do
| things otherwise, follow the principle of "least surprise". In
| fact vibe coding adds another layer to this - an LLM generally
| expects the most common pattern - and so maintenance and testing
| will be orders of magnitude easier.
| hexbin010 wrote:
| It's a trade off like everything. More DTOs means more mapping,
| more coming up with names, more files etc. There's definitely a
| middle ground.
|
| You can (should) also apply it selectively. Eg for auth, I'd
| never want a single UserDTO used for creating and displaying a
| user - creating a user requires a password, a field you don't
| want when retrieving a user, to avoid mistakes.
|
| I know DDD advocates would say that you're then not being true to
| DDD, but yes that's business. It's very very hard to get everyone
| to agree with the reduced velocity of 100% adherence to DDD for
| an extended period. In my experience it starts off as "this is
| great" then people start hate reviewing PRs for simple changes
| that have 28 new files (particularly in Java) and they quietly
| moan to the boss about being slowed down by DDD
| codemonkey-zeta wrote:
| Author is on the verge of having a Clojure epiphany.
|
| > 1. You should often be using different objects in different
| contexts.
|
| This is because "data" are just "facts" that your application has
| observed. Different facts are relevant in different
| circumstances. The User class in my application may be very
| similar to the User class in your application, they may even have
| identical "login" implementations, but neither captures the
| "essence" of a "User", because the set of facts one could observe
| about Users is unbounded, and combinatorially explosive. This
| holds for subsets of facts as well. Maybe our login method only
| cares about a User's email address and password, but to support
| all the other stuff in our app, we have to either: 1. Pass along
| every piece of data and behavior the entire app specifies 2.
| Create another data object that captures only the facts that
| login cares about (e.g. a LoginPayload object, or a LoginUser
| object, Credential object, etc.)
|
| Option 1 is a nightmare because refactoring requires taking into
| consideration ALL usages of the object, regardless of whether or
| not the changes are relevant to the caller. Option 2 sucks
| because your Object hierarchy is combinatorial on the number of
| distinct _callers_. That's why it is so hard to refactor large
| systems programmed in this style.
|
| > 3. The classes get huge and painful.
|
| The author observed the combinatorial explosion of facts!
|
| If you have a rich information landscape that is relevant to your
| application, you are going to have a bad time if you try modeling
| it with Data Objects. Full stop.
|
| See Rich Hickey's talks, but in particular this section about the
| shortcomings of data objects compared to plain data structures
| (maps in this case).
|
| https://www.youtube.com/watch?v=aSEQfqNYNAc
| bccdee wrote:
| > Option 2 sucks because your Object hierarchy is combinatorial
| on the number of distinct _callers_.
|
| I kinda like that. Suppose we do something like `let mut authn
| = UserLoginView.build(userDataRepository); let session =
| authn.login(user, pwd)`. You no longer get to have one
| monolithic user object--you need a separate UserDataRepository
| and UserLoginView--but the relationship between those two
| objects encodes exactly what the login process does and doesn't
| need to know about users. No action-at-a-distance.
|
| I've never used clojure, but the impression I get of its "many
| functions operating over the same map" philosophy is that you
| trade away your ability to make structural guarantees about
| which functions depend on which fields. It's the opposite of
| the strong structural guarantees I love in Rust or Haskell.
| codemonkey-zeta wrote:
| > you trade away your ability to make structural guarantees
| about which functions depend on which fields
|
| You might make this trade off using map keys like strings or
| keywords, but not if you use namespace qualified keywords
| like ::my-namespace/id, in combination with something like
| spec.alpha or malli, in which case you can easily make those
| structural guarantees in a way that is more expressive than
| an ordinary type system.
| oftenwrong wrote:
| There is something to be said for having some basic data access
| libraries already in place, even if they are not ideal, so that
| developers can bang out functionality more quickly. That is the
| typical selling point of ORMs, isn't it? While there are well-
| known downsides, you can skip the ORM when it's not a good fit,
| or later when you realise that it is causing a problem.
|
| Generally, I prefer to create functions for specific queries,
| rather than for specific "entity" types, and the return type of
| each query matching the result of the query. This fits with the
| reality that queries often involve multiple entity types.
|
| My favourite application-later database tool so far is
| https://www.jooq.org/ because it allows for code generation from
| the database schema, allowing for type-safe construction of
| queries. I find this makes it easier to create and maintain
| queries. It is a relatively unopinionated power tool, with
| minimal attempts at "automagic" behaviour. I find myself missing
| jOOQ now that I am not working much with Java.
| rokkamokka wrote:
| In my experience (in our rather large MVC-style Laravel code
| base) DTOs are almost always an unnecessary abstraction. I'm much
| more content just shuffling actual Models around, with small
| methods that map these to whatever format the client then
| requires. I've refactored away many a DTO added by junior
| developers and the code is always much simplified.
| Noumenon72 wrote:
| I'm not sure I understand the different approaches being compared
| here. Are you opposing 1 and supporting 2? 1.
| HatsService with methods .get_hats(), .throw_hats() 2.
| wearable_hats = db.query(Hats).map(hat => WearableHatDto(hat)
| throwable_hats = db.query(Hats).map(hat => ThrowableHatDto(hat)
___________________________________________________________________
(page generated 2025-11-01 23:01 UTC)