Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The main problem with ORM is the "object" part. I feel object-oriented programming had its chance and it failed. Both event sourcing and relational models (="table modelling") are better models for programming systems.

So by using an ORM, you are (at least in the name, and as traditionally done) taking a better programming model (relational models) and mapping it to an inferior one (OOP) -- also getting a ton of leaky abstractions involved too.

I wish there were more "ORMs" that were made with the assumption that Object-Oriented Programming is BAD, and started out with assumptions of wanting to program either in an event sourcing model and/or a relational model, and see where that would take things.

Related:

Fred Brooks, The Mythical Man Month (1975): "Show me your flowchart and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowchart; it'll be obvious."

Out of the tar pit paper: https://curtclifton.net/papers/MoseleyMarks06a.pdf

--

My take on the software industry today is that OOP failed to produce good and maintainable code, but instead of fixing that, one is adding the idea of micro-services to try to contain the damage done by OOP.

If more people moved to event sourcing, functional code and focusing on data over code, there would be much less need for micro-services etc (or, they just become less relevant).



I think the "O" in ORM doesn't really share all that much relation to OOP. Many can be called TRM ("Type-relational mapping") as well. "Object" is one of those words that's so overloaded with different meanings I try to avoid using it.

Event sourcing is pretty complex; I'm hesitant to use it unless I have a clear and specific reason for it. It's not mutually exclusive with a ORM either; I've seen people use an ORM to query and even build their projections for example.


I don't disagree with anything of what you say really.

But I think perhaps that "event sourcing is pretty complex" may be due to programming languages and ORM having the object-oriented mindset as the "default" usage pattern. If we had 30 years of tooling evolution and education exposure for event sourcing I do not think it would be "complex".

(Also I mean event sourcing as in "how does one model data in the SQL database", not whether one is doing event-driven architectures with distribtion, async, event brokers etc -- that IS adding a lot of complexity but is something else..)


I'm not so sure about that; you either:

1. You have a table with records; to change something it's one "update record".

2. You have an append-only log of everything that happens, and a table of records which represents the current state after every log item is played back for efficient querying. To change something you do "insert log + update projection_record".

It seems to me the second item is fundamentally more complex, no matter what you do. You can abstract some of that away with good tooling, but things like migrations will probably forever be a right pain with event sourcing.

Sometimes all of that is worth it, but often it's not. That's probably why it's not the default way to write applications.


When I said development of tooling of 30 years, I didn't mean a bunch of scripts or libraries, it would include things such as databases and programming languages. Certainly things like automating database migrations is things can be fixed in that timespan on tooling -- and probably WILL be fixed in coming 30 years.

The thing about "update projection_record" is that any business logic in there can be done fully declarative/functional, making it easier to develop, reduce bugs, evolve it etc etc.

Sure if you actually have to program "insert log + update projection_record" it's more fragile and complex. But you are basically doing things the compiler+database should have been doing for you transparently if it was the kind of event sourced tooling I describe.

If you can just say in one part of the program "insert log"..

..and independently of that say "I need to know this to make a decision, it can be computed from the logs like this" -- and change those questions as you like, and it is the job of some combination of DSL/declarations/compiler/database to make sure that the right projections are maintained to efficiently evaluate the expression, then I don't think that is more complex.

This is the main idea in the Out of the Tar Pit paper I linked to. Also e.g. the Materialize database has some ideas like this (it can't be used in many situations, just an example of a database that has this kind of idea).


You mention the issue being with OOP, but wouldn't you run into the same issue with any nested structure? I think the "impedance mismatch" as it's called is that in programming languages parent elements point to their children, but in relational models, it's the children that point back to their parent (foreign keys).


Hmn. Good point. I feel a more important impedance mismatch is one of transactions/concurrency.

If your model is ORM OOP such as:

    user := LoadUser(db, uid)
    user.SetName("Alice")
    user.Save()
the impedance mismatch is really that the "user" you have in the memory in the backend actually isn't the real user. After doing SetName() it isn't committed to the database, other processes will still see the old name and so on.

I mean, just at the point you have loaded the user there is (I feel..) an impedance mismatch if you think about the "user" object as "the user", instead of simply "a query result of userInfo that may already be stale".

If you drop the ORM and just execute "update User set Name = @newName where Id = @uid and Name = @oldName" there is no impedance mismatch. Either you do the update or someone raced you and you did not. There is no stale data anywhere to worry about.

Perhaps OOP isn't the sole reason for the "fetch/modify/save" programming style, but I think the OOP way of thinking of the world quickly lead to that kind of solution approach.

A database supports "query" and "apply a transaction to move from one version of the data to the next version".

OOP-focused use of ORM thinks more about "loading" and "saving". That is where the impedance mismatch is I feel.

OOP => there is such things as "objects" => objects must be "loaded" and "saved" => mismatch.

If one doesn't need to fetch/modify/save, but instead things in terms of queries and transactions, then you also don't need to care about "nested structure" nearly as often. I don't use ORMs and it just doesn't come up that much -- why would I load the nested structures to the backend?..

Although I agree with what you say that there is a fundamental mismatch between the efficient way to store nested structures in backend vs DB.


I’m curious - how do you use “relational models” as a programming model? I get it for data storage and retrieval, but you eventually also need functions/methods to operate on that data. So, how would you organize that code… relationally?


I just find that when organizing code it makes most sense to organize it after what it does. Example:

E.g., an enrollment flow may be setting the user's name for one reason, and a database import job fetch the user's name for a second reason, and an internal support tool the set the user's name for a third reason.

In either case the job of the backend is to "set the name", to take an action. (Either by inserting a row to UserNameChangedInSupportTool, or by doing an UPDATE to User.Name, depending on whether you are doing event sourcing or not).

Typically the "set the name" is a very small detail in a larger context (enrollment flow; support tool use; import job). So in this example I would likely organize the code after those tasks done / problems solved.

My point is really that in these 3 functions I find it doesn't lead to cleaner code to have some sort of mirror (in principle out-of-date the moment you load it) of the "User object" in the memory, and modify it, and "persist" it

Just focus on what you need to do (like, "change the name of the user") and execute that towards the database from the Function you are performing. The User lives in the database, not in an object in your backend.

I.e., "update User set Name = @newName where Id = @uid and Name = @oldName" (action to change the user's name, if no-one raced us), instead of "load user", "set user name", "save user" (OOP, with an impedance mismatch because there is one version in your backend and another in the DB)


Okay, I think I get what you're trying to say now. I hate... hate... hate... ORMs where you alter objects and then let the ORM magically persist the changes to the database. That is just a level of abstraction that I'm not comfortable with. I think it pushes the developer too far away from the data layer.

I still think that the object model does have utility, but like everything, when taken too the logical ends, you end up with ORM managed objects.

Instead I've always liked using a Service pattern, where you have data objects that are (largely) immutable. Then you have Service objects that contain the methods that operate on the data objects.

I'm just not sure I'd have called it a relational model (hence my confusion).


> I feel object-oriented programming had its chance and it failed. > Object-Oriented Programming is BAD

mind elaborating why OOPS is BAD and failure?


I guess I worded things a bit sloppily, OOP can mean a lot of different things too..

The main thing is that working with state is hard. "Objects" is often about representing something stateful and encourages thinking in terms of doing actions to state. It embraces a stateful world-view..

Functional programming I guess is the one popular alternative; but a lot of the benefits of functional programming can also be had in imperative languages, if one e.g. thinks in terms of event sourcing.

My definitions; If your object is named UserNameChanged, and you write code to process such events and so on, that is event sourcing. While if you structure your code to say that a "User" has a "Name" property, that is OOP.

Focusing on the events, state transitions etc as the first class thing gets you closer to a "stateless" programming model.

The Out of the tar pit paper I linked to describes the problems of working with state. And I think Event Sourcing is a more real-world, pragmatic approach to the same problems described in that paper.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: