4 ms·
I've come to the exact opposite conclusion. The query DSL for ORMs is fine, works well in a large majority of the situations. When it gets problematic, you can
by spion 7y ago
I've come to the exact opposite conclusion.
The query DSL for ORMs is fine, works well in a large majority of the situations. When it gets problematic, you can almost always drop down to pure SQL.
The mapping part of the ORM on the other hand is a disaster. Table rows make poor OO classes. The best OO classes are "workers" that have some concrete task at hand. "Active record" style classes have no scope whatsoever - anything connected to the data can be potentially added to the model class.
So on projects with ORMs my main problem is dealing with the responsibility spaghetti. Typically one class which is the central point of the domain grows to crazy proportions. In a document-related software for example, you can potentially put everything in a "Document" entity class. "DocumentConverter" on the other hand has clear responsibility scope - to convert documents - but of course, a converter doesn't have a database table.
- derefr 7y agoSo don't map DB records to objects, then. DB records are records, and their proper typing in your business logic is as records—chunks of plain old data, strongly-typed, that your (OO or otherwise) code can declare DB-side interfaces against. The only responsibilities of the module/class that owns the record type, should be getting things converted into and out of that record type. Plain-old-data DB record types (and their respective owner modules) are to ORMs, as code-gen'ed wire record types (and their respective owner modules) are to ProtoBuf-like wire-format codec libraries. They're the containers you put data into and take it out of, to ensure it's in the right types when being sent to, or received from, your DB. No more than that. And, corollary to this idea: the POD record-type MyDB.FooTable shouldn't be taken as a type to use to stuff in the results from any random query you run against the DB table `foo`. It should be taken to represent the specific row type that the DB table `foo` has—the one you get when you `SELECT * from foo`. If you do an SQL query, and the result of that SQL query has fewer or extra or differently-typed columns, then the resulting row-set will have a different record type, and so you should have a separate POD data-type that does match these records. (Example: if you join two tables with a query, you don't need the ORM to spit out two records objects with a fancy OO-style relationship between them; you just need one record type that represents the resulting joined rows, without any particular idea that it happens to be an amalgamation of data from two tables.) For an example of this approach to ORM, see Elixir's https://github.com/elixir-ecto/ecto https://github.com/elixir-ecto/ecto.
- spion 7y agoWould that be a "RRM" (Record-Relational mapper)? If so we're already on board (in a different language). P.S. I bet you would never guess which typed language with great support for records makes it easy (for the most part) to build most type-safe SQL queries on the fly, even with projections, without explicitly defining types for every possible projection variation.
- zbentley 7y agoLots of Python codebases are on board with that (and I assume codebases in other languages are as well). It's by convention rather than language-level support, but works perfectly well: don't bolt business logic onto your ORM classes' methods, handle those elsewhere in a separate layer whose conventions you define. Works great.
- joshlemer 7y agoWould you happen to be talking about TypeScript?
- spion 7y agoyes
- mpajunen 7y agoAny TypeScript libraries you are using?
- spion 7y agoWe used https://github.com/brianc/node-sql https://github.com/brianc/node-sql but it unfortunately stopped being maintained, so now we maintain our own fork.
- thoman23 7y agoI get that you're talking about Typescript, but I just want to say that Scala excels in this area as well. I found Slick to be a pleasure to work with once I embraced not trying to map directly from a relational model to an object model, but instead to just use Scala's ability to manipulate and reshape the relational model as needed.