Wednesday, April 28, 2010

MongoDB with Sculptor - Repository

This post is part of a series of articles describing the support for MongoDB in Sculptor. This post explains how Repositories with useful operations are defined and how queries can be expressed in terms of the domain.

In the visualization of the blog sample you maybe noticed that there are Services with findById, findAll, save and delete even though they were not explicitly defined in the model. They come from that the domain objects are marked with with scaffold. That automatically generates some predefined CRUD operations in the Repository and corresponding Service.


Entity BlogPost {
scaffold
- Blog inBlog
String slug key
String title
String body
DateTime published nullable
Set<String> tags
- Author writtenBy
- List<Comment> comments opposite forPost
}


You can also define the needed operations explicitly like this:


Entity BlogPost {
- Blog inBlog
String slug key
String title
String body
DateTime published nullable
Set<String> tags
- Author writtenBy
- List<Comment> comments opposite forPost

Repository BlogPostRepository {
save;
delete;
findAll(PagingParameter pagingParameter);
findById;
findByKey;
List<@BlogPost> findPostsInBlog(@Blog blog);
List<@BlogPost> findPostsWithComments => AccessObject;
List<@BlogPost> findPostsWithTags(Set<String> tags);
protected findByCondition;
}


You don't need to do any manual coding for the built in operations, such as save, delete, findAll, findById, findByKey.
findByKey is for the natural key, i.e. the attributes marked with key (slug above).

There is good support for pagination and sorting.

As you see in the above sample it is also possible to define your own repository operations, such as findPostsWithTags.

findByCondition

The most interesting finder is findByCondition. Queries can expressed with an internal DSL that support code completion and refactoring. It is best illustrated with a few samples.


public List<BlogPost> findPostsInBlog(Blog blog) {
List<ConditionalCriteria> condition = criteriaFor(BlogPost.class)
.withProperty(inBlog()).eq(blog)
.orderBy(published()).descending()
.build();
return findByCondition(condition);
}



public List<BlogPost> findPostsWithGreatComments() {
List<ConditionalCriteria> condition = criteriaFor(BlogPost.class)
.withProperty(comments().title()).ignoreCaseLike(".*great.*")
.and().withProperty(published()).isNotNull()
.orderBy(published()).descending().build();
return findByCondition(condition);
}


The ConditionalCriteriaBuilder has support for defining queries with eq, like, between, lessThan, greaterThan, in, and, not, orderBy, and some more.

You can of course also work directly with the underlaying MongoDB DBCollection and DBObject for doing advanced queries that might not be supported by findByCondition.

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 {
not aggregateRoot
- 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<HandlingEvent> events opposite cargo;

}

BasicType TrackingId {
String identifier key
}

ValueObject Itinerary {
belongsTo Cargo
- Cargo cargo nullable opposite itinerary
- List<Leg> 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<@Media> 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
}

MongoDB with Sculptor - Data Mapper

This post is part of a series of articles describing the support for MongoDB in Sculptor. This post explains how the mapping between domain objects and MongoDB data objects is performed.

When working with mongoDB Java API the DBObject plays a central role. It is like a key value Map. Values can be of most types and also collections and other DBObjects for nested documents. Sculptor also adds support for Enums and Joda date time classes.

Implementing mapping of domain objects to DBObjects is a tedious and error prone programming task that is a perfect candidate for automation. Sculptor comes in handy for this. Sculptor generates data mapper classes that converts domain objects to DBObjects. It is useful to not have to write those mappers by hand.

An early definition of a BlogPost in the blog sample might look like this in Sculptor model:

Entity BlogPost {
String slug key
String title
String body
}


From this Sculptor generates domain object class in java with appropriate getters, setters, constructors, equals, etc. Sculptor also generates a mapper for converting the BlogPost to/from DBObject. The generated code looks like this:


public DBObject toData(BlogPost from) {
if (from == null) {
return null;
}

DBObject result = new BasicDBObject();

if (from.getId() != null) {
ObjectId objectId = ObjectId.massageToObjectId(from.getId());
result.put("_id", objectId);
}

result.put("slug", from.getSlug());
result.put("title", from.getTitle());
result.put("body", from.getBody());

return result;
}


and in the other direction:


public BlogPost toDomain(DBObject from) {
if (from == null) {
return null;
}

String slug = (String) from.get("slug");

BlogPost result = new BlogPost(slug);

if (from.containsField("_id")) {
ObjectId objectId = (ObjectId) from.get("_id");
String idString = objectId.toStringMongod();
IdReflectionUtil.internalSetId(result, idString);
}

if (from.containsField("title")) {
result.setTitle((String) from.get("title"));
}
if (from.containsField("body")) {
result.setBody((String) from.get("body"));
}

return result;
}


I'm happy that I don't have to write and especially maintain that kind of code. The generation is not a one time shot. When you change the model the domain objects and the mappers are regenerated.

Domain objects can of course also have behavior, otherwise it wouldn't be a rich domain model. The behavior logic is always written manually. Separation of generated and manually written code is done by a generated base class and manually written subclass, a gap class. It is in the subclass you add methods to implement the behavior of the domain object. The subclass is also generated, but only once, it will never be overwritten by the generator.

You might need to add some hand written code to customize the mappers. That is easy. In the model you can specify that you need a subclass that you can implement yourself. This is very useful for doing data migration.

It can also be good to know that fields marked with transient are are not stored, but they are loaded if they exist in the retrieved documents. This can also be used for data migration.

By default the names in the data store are the same as the names in the Java domain objects. In case you need to use other names it is possible to define that in model with databaseTable and databaseColumn.


Entity BlogPost {
databaseTable="BlogEntries"
String slug key
String title
String body databaseColumn="content"
}


You have probably selected MongoDB for its low latency. Then you want the data mapper to be fast also. The mappers provided by Sculptor are generated code that runs at full speed. Alternative solutions using reflection or intermediate String JSON format is probably not as fast.

Tuesday, April 27, 2010

MongoDB with Sculptor - Introduction

MongoDB bridges the gap between key-value stores (which are fast and highly scalable) and traditional RDBMS systems (which provide rich queries and deep functionality).

Would you like to?
  • Use a rich persistent domain model à la DDD with automatic mapping to MongoDB data structures.
  • Use associations even though the underlaying data store is not relational.
  • Express queries in refactoring safe terms of the domain model.
  • Get CRUD operations, and GUI, for free.
Then you should try Sculptor with the brand new MongoDB target implementation.

In the next few days I will describe the features one-by-one, but first an brief overview of what Sculptor provides for efficient development of a typical enterprise or web application with MongoDB as persistent store.

Sculptor is a productivity tool that let you express your design intent in a textual DSL, from which Sculptor generates high quality Java code and configuration. Read more in What is Sculptor?

Sculptor generates data mapper classes that converts domain objects to/from MongoDB data structures, DBObjects.

Sculptor provides generic repository operations for use with MongoDB. This includes operations such as save, delete, findById, findByKey, findByCondition, and some more.

Queries can be expressed with a slick fluent api that support code completion and refactoring.

Rich support for associations. Aggregates are stored as a embedded documents. Other associations are stored with referring ids. 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.

A sample blog application may look like this when defined in Sculptor textual DSL:


Visualization, generated by Sculptor, of the above model looks like this:


Read the other posts that describes the features in more detail:

More hands on instructions of how to use Sculptor is available in the Sculptor MongoDB Tutorial.