It’s been a while since I’ve ranted on this blog, but I was recently challenged by a reddit thread to write about this topic, so here goes…
So, you’re writing a service that produces some JSON from your database model. What do you need? Let’s see:
That’s it. That’s all there is to it. Only basic SQL-92, enhanced with some vendor-specific JSON export syntax. (There are also SQL standard JSON APIs as implemented in other RDBMS). Let’s discuss it quickly:
And now, you’re getting:
- Read a book on DDD
- Read another book on DDD
- Write some entities, DTOs, factories, and factory builders
- Discuss whether your entities, DTOs, factories, and factory builders should be immutable, and use Lombok, Autovalue, or Immutables to ease the pain of construction of said objects
- Discuss whether you want to use standard JPA, or Hibernate specific features for your mapping
- Plug in Jackson, the XML and JSON mapper library, because you’ve read a nice blog post about it
- Debug 1-2 problems arising from combining Jackson, JAXB, Lombok, and JPA annotations. Minor thing
- Debug 1-2 N+1 cases
STOP IT
No, seriously. Just stop it right there! What you needed was this kind of JSON structure, exported form your favourite Sakila database:[{ "first_name": "PENELOPE", "last_name": "GUINESS", "categories": [{ "name": "Animation", "films": [{ "title": "ANACONDA CONFESSIONS" }] }, { "name": "Family", "films": [{ "title": "KING EVOLUTION" }, { "title": "SPLASH GUMP" }] }] }, { ...In English: We need a list of actors, and the film categories they played in, and grouped in each category, the individual films they played in. Let me show you how easy this is with SQL Server SQL (all other database dialects can do it these days, I just happen to have a SQL Server example ready:
-- 1) Produce actors
SELECT
a.first_name,
a.last_name, (
-- 2) Nest categories in each actor
SELECT
c.name, (
-- 3) Nest films in each category
SELECT title
FROM film AS f
JOIN film_category AS fc ON f.film_id = fc.film_id
JOIN film_actor AS fa ON fc.film_id = fa.film_id
WHERE fc.category_id = c.category_id
AND a.actor_id = fa.actor_id
FOR JSON PATH -- 4) Turn into JSON
) AS films
FROM category AS c
JOIN film_category AS fc ON c.category_id = fc.category_id
JOIN film_actor AS fa ON fc.film_id = fa.film_id
WHERE fa.actor_id = a.actor_id
GROUP BY c.category_id, c.name
FOR JSON PATH -- 4) Turn into JSON
) AS categories
FROM
actor AS a
FOR JSON PATH, ROOT ('actors') -- 4) Turn into JSON
- The outer most query produces a set of actors. As you would have expected
- For each actor, a correlated subquery produces a nested JSON array of categories
- For each category, another correlated subquery finds all the films per actor and category
- Finally, turn all the result structures into JSON
- Whatever you thought your DDD “root aggregate was”
- Your gazillion entities, DTOs, factories, and factory builders
- Your gazillion Lombok, Autovalue, or Immutables annotations
- Your hacks and workarounds to get this stuff through your standard JPA, or Hibernate specific features for your mapping
- Your gazilion Jackson, the XML and JSON mapper library annotations
- Debugging another 1-2 problems arising from combining Jackson, JAXB, Lombok, and JPA annotations
- Debugging another 1-2 N+1 cases
Don’t go mapping that stuff in the middleware if you’re not consuming it in the middleware.Oh, want to switch to XML? Easy. In SQL Server, this amounts to almost nothing but replacing JSON by XML:
SELECT
a.first_name,
a.last_name, (
SELECT
c.name, (
SELECT title
FROM film AS f
JOIN film_category AS fc ON f.film_id = fc.film_id
JOIN film_actor AS fa ON fc.film_id = fa.film_id
WHERE fc.category_id = c.category_id
AND a.actor_id = fa.actor_id
FOR XML PATH ('film'), TYPE
) AS films
FROM category AS c
JOIN film_category AS fc ON c.category_id = fc.category_id
JOIN film_actor AS fa ON fc.film_id = fa.film_id
WHERE fa.actor_id = a.actor_id
GROUP BY c.category_id, c.name
FOR XML PATH ('category'), TYPE
) AS categories
FROM
actor AS a
FOR XML PATH ('actor'), ROOT ('actors')
<actors> <actor> <first_name>PENELOPE</first_name> <last_name>GUINESS</last_name> <categories> <category> <name>Animation</name> <films> <film> <title>ANACONDA CONFESSIONS</title> </film> </films> </category> <category> <name>Family</name> <films> <film> <title>KING EVOLUTION</title> </film> <film> <title>SPLASH GUMP</title> </film> </films> </category> ...It’s so easy with SQL! Want to support both without rewriting too much logic? Produce XML and use XSLT to automatically generate the JSON. Whatever.