Query data across slices
Goal: a screen needs data that doesn’t live in a single slice — authors with their books, an order with its line items, anything where one thing owns a list of another.
Keep the relationship explicit
Section titled “Keep the relationship explicit”In a database-backed Arc slice, model the relationship the same way you would in the domain: the child carries the parent’s id, and the query filters by it. Arc’s part is making that query a typed, live endpoint and generating the React proxy that calls it.
These are fragments of the standalone MongoDB book lesson; use its domain declarations, imports, replica-set configuration, and generated proxies. Neither the relationship nor the live query requires Chronicle.
For an owns-a-list relationship, keep the child keyed to its parent. A book belongs to an author, so the Book read model carries AuthorId:
[ReadModel]public record Book(BookId Id, AuthorId AuthorId, BookTitle Title){ public static ISubject<IEnumerable<Book>> BooksForAuthor(AuthorId authorId, IMongoCollection<Book> books) => books.Observe(book => book.AuthorId == authorId);}@ReadModelpublic record Book(BookId id, AuthorId authorId, BookTitle title) { public static Flow.Publisher<List<Book>> booksForAuthor( AuthorId authorId, @FromServices MongoObservableQuery queries ) { return MongoObservations.observe( queries, Book.class, Criteria.where("authorId").is(authorId.value())); }}@ReadModeldata class Book(val id: BookId, val authorId: AuthorId, val title: BookTitle) { companion object { @JvmStatic fun booksForAuthor( authorId: AuthorId, @FromServices queries: MongoObservableQuery ): Flow<List<Book>> = queries.observe<Book>(Criteria.where("authorId").`is`(authorId.value())) }}import { field } from '@cratis/fundamentals';import { argument, query, readModel, service, type ObservableSource } from '@cratis/arc.core';
@readModel()export class Book { @field(BookId) id!: BookId; @field(AuthorId) authorId!: AuthorId; @field(BookTitle) title!: BookTitle;
@query({ observable: true }, argument('authorId', AuthorId), service(BookRepository)) static booksForAuthor(authorId: AuthorId, books: BookRepository): ObservableSource<Book[]> { return books.observeForAuthor(authorId); }}No attribute marks the identity — by convention the Id property is the read model’s identity. Arc separates the query method’s parameters into data and services: authorId is data, so it becomes a query parameter; the collection or MongoObservableQuery is a service, so it’s injected. C# tells them apart by type; the JVM marks service parameters with @FromServices and leaves caller arguments unannotated, and puts the query on the read model’s companion object as a @JvmStatic function. The generated proxy takes the data parameter either way:
// authorId is the generated Guid value, not String(author.id).const [books] = BooksForAuthor.use({ authorId });With MongoDB change streams configured, Observe(...) publishes updated data. The filter does not enforce parent existence; use server validation and database constraints for that separate requirement. Add a book for that author and the row updates without polling.
For a list-with-details screen, let the author list call AllAuthors.use() and each row call BooksForAuthor.use({ authorId: author.id }). That keeps each slice small: the author slice owns authors, the book slice owns books, and the screen composes their generated proxies.
See also
Section titled “See also”- Queries — observable and non-observable query methods, parameters, and filtering.
- Relate your slices — this relationship built step by step.
- Execute a command from React — consuming the query proxy.