References
Working with entity relationships in a DDD-friendly way
References in JustScale allow you to work with entity relationships without exposing raw IDs. The repository is the abstraction for resolving references - how it fetches the entity is an implementation detail.
Creating References
Use the Model.ref tagged template literal to create references:
import { Author, Post } from './models';
// Create a reference from an entity
const authorRef = Author.ref`${author.id}`;
// Use in relationships
const post = await postRepo.insert({
title: 'My Post',
status: 'draft',
author: authorRef, // Reference, not raw ID
});Resolving References
The repository is the abstraction for resolving references. This is the DDD-friendly way - you work with references, not IDs:
// Resolve a single reference
const authorRef = Author.ref`${someId}`;
const author = await authorRepo.resolve(authorRef);
// Batch resolve multiple references (single query)
const refs = [
Author.ref`${id1}`,
Author.ref`${id2}`,
Author.ref`${id3}`,
];
const authors = await authorRepo.resolveMany(refs);
// Resolve a reference from a loaded entity
const article = await articleRepo.findById('...');
const author = await authorRepo.resolve(article.author);Why on the Repository?
- Repository is the contract for entity access
- Implementation is hidden (Postgres today, Redis tomorrow)
- You ask "give me this Author" and the repository figures out how
- Keeps IDs as an implementation detail of the storage layer
Lazy Loading
References loaded from the database are automatically awaitable. Just await them to fetch the related entity:
// Load an article
const article = await articleRepo.findById('...');
// The author field is a Reference - just await it
const author = await article.author;
console.log(author.name);
// Or chain it
const email = (await article.author).email;Eager Loading
For lists, lazy loading causes N+1 queries. Use the load option to batch-fetch references:
// Without eager loading - N+1 queries!
const posts = await postRepo.find({ where: status.eq('published') });
for (const post of posts) {
const author = await post.author; // One query per post
}
// With eager loading - 2 queries total
const posts = await postRepo.find({
where: status.eq('published'),
load: ['author'], // Batch fetch all authors
});
for (const post of posts) {
const author = await post.author; // Already loaded, no query
}Object Form
Use object form for selective loading:
await postRepo.find({
where: status.eq('published'),
load: { author: true }, // Only load author
});Combining with has()
Use .has() to filter on related entity fields, and combine with load for eager loading:
// Find posts by premium authors AND eager load those authors
const posts = await postRepo.find({
where: Post.fields.author.has(
Author.fields.tier.eq('premium')
),
load: ['author'], // Authors already filtered, now also loaded
});
// All loaded - no additional queries
for (const post of posts) {
const author = await post.author;
console.log(`Premium author: ${author.name}`);
}How It Works
Under the hood, eager loading:
- Main query fetches the primary entities
- Collects unique reference IDs from results
- Single batch query:
SELECT * FROM authors WHERE id = ANY($1) - Stores in local cache
- Pre-populates Reference objects from cache