MongoDB with Sculptor - Associations
This post is part of a series of articles describing the support for MongoDB in Sculptor. This post explains how associations and inheritance are managed.
MongoDB is not a relational database and there is no such thing as joins. You can still use associations between domain objects. Associations can be of two main categories, either embedded or reference by id.
Aggregates
Aggregates are one of the core building blocks in Domain-Driven Design (DDD). MongoDB has perfect support for aggregates, since an associated object can be stored as an embedded document, i.e. it belongs to parent object and cannot be shared between several objects.
Let us repeat what DDD says about aggregates:
An Aggregate is a group of associated objects which are considered as one unit with regard to data changes. The Aggregate is demarcated by a boundary which separates the objects inside from those outside. Each Aggregate has one root. The root is an Entity, and it is the only object accessible from outside. The root can hold references to any of the aggregate objects, and the other objects can hold references to each other, but an outside object can hold references only to the root object. from DDD Quickly
Sculptor will validate the reference constraints described in the quote above. Repositories are only available for aggregate roots. Aggregates are defined with belongsTo
or not aggregateRoot
in the owned DomainObjects.
A typical aggregate in the blog sample is that Comment
belongs to BlogPost
Entity BlogPost {
String slug key
String title
String body
DateTime published nullable
- List comments opposite forPost
}
ValueObject Comment {
belongsTo BlogPost
- BlogPost forPost opposite comments
String title
String body
}
An aggregate can of course include several classes as in this sample:
Entity Cargo {
- TrackingId trackingId key
- Location origin required
- Location destination required
- Itinerary itinerary nullable opposite cargo
- Set events opposite cargo
}
BasicType TrackingId {
String identifier key
}
ValueObject Itinerary {
belongsTo Cargo
- Cargo cargo nullable opposite itinerary
- List legs
}
ValueObject Leg {
belongsTo Cargo
- CarrierMovement carrierMovement
- Location from
- Location to
}
In above sample the TrackingId
, Itinary
and Leg
are all stored toghether with the Cargo
. BasicTypes are also stored as embedded documents.
Reference by Id
The other alternative is to store ids of the referred objects. In the domain objects there are generated getters that lazily fetch associated objects from the ids. This means that you don’t have to work with the ids yourself, you can follow associations as usual, but be aware that an invocation of such a getter might need to query the database.
Set writers = blog.getWriters();
In the same way you can modify unowned associations by working with objects rather than ids.
Author pn = new Author("Patrik");
pn = authorService.save(getServiceContext(), pn);
blog.addWriter(pn);
Author ak = new Author("Andreas");
ak = authorService.save(getServiceContext(), ak);
blog.addWriter(ak);
blogService.save(getServiceContext(), blog);
Referential integrity of the stored ids are not enforced. It must be handled by your program. Lazy getters of associations will not fail if referred to object is missing, they will return null for single value references and ignore missing objects for collection references. This means that you can cleanup dangling references by fetching objects, populate associations by invoking the getters and then save the object.
Inheritance
The MongoDB feature of Sculptor has full support for inheritance. It is even possible to do polymorphic queries.
abstract Entity Media {
String title !changeable
Repository MediaRepository {
List findByTitle(String title);
protected findByCondition;
}
}
Entity Book extends @Media {
String isbn key length="20"
}
Entity Movie extends @Media {
String urlIMDB key
Integer playLength
- @Genre category nullable
}