Great to see some thought on how to use Meteor for large-scale applications!
Please forgive me for dumping some slightly unstructured thoughts here
CQRS
First let me suggest a perspective: looking at Command Query Responsibility Separation as an inspiration for API design for Meteor. Iâm always kind of surprised that the term CQRS hardly ever shows up in discussions of architecture in Meteor, because Meteor is a pretty awesome modern CQRS framework. Let me briefly describe the important parallels:
A âtraditionalâ non-CQRS has the server going through âdomain logicâ code for all its requests. An app with an Active Record style for example, has you using Post objects loaded with query logic in your update / create / remove stuff and it has you loading those same objects with update / create / remove logic even when youâre just displaying them in a list. CQRS tells us to separate our app logic into a command part and a query part. And guess what: Meteor methods are command handlers, and Meteor publications are a thin read layer. BOOM, youâve got CQRS! But like a rocket-powered version, because the publications use livequery and get pushed from the server to the client for auto-updating goodness, oh my! (I often feel that an understanding of CQRS is why Iâm often a lot less confused about Meteorâs architecture than a many of my colleagues) Also, MongoDB is an excellent database for denormalised data storage that goes very well with this architecture.
Now given that Meteor apps are basically CQRS apps, this means we can use lessons learned in the CQRS world to improve Meteor! Some examples:
Middleware
Given that Meteor Methods correspond to the POST / PUT / DELETE part of an application, it would be nice to use the middleware pattern to for purposes like logging, access control, maybe early validation.
Domain Driven Design + Optimistic UI
While CQRS is a natural fit with Domain Driven Design, the proposed validated-method
makes you separate validation and behaviour, rather than coupling them like you would in a DDD app. Iâm still searching for a pattern to couple this and still use Meteorâs client simulation.
Example: I have a form. Upon submission I would like to run domain code to perform domain behaviour and throw errors if necessary. Obviously I want all this to run on the server for security reasons, but for a quick response I also want to run it on the client. Now, on the client I want to catch potential errors from the simulation and the server, and use them to create user feedback on the form view. But oddly, when I listen for errors, Meteor decides to wait for the server response rather than trusting the simulation? From this point on I could just as well have left my domain code in /server
instead of in /lib
The Meteor guide considers the optimal Meteor method to be one that waits for client validation until it goes to the server. However, for the applications I build, the correctness of my code (hence DDD) is more important than a bit of server resources.
In the guide, I see the usage of two options returnStubValue
and throwStubExceptions
, but I donât see them in the Meteor documentation. They sound like they would allow me to make methods behave like I had expected their default behaviour to be. Are these 1.3
features or undocumented 1.2
features?
Persistence
Iâve learned that when working with CQRS, NoSQL and Meteor: it can really pay off to start thinking about persistence very differently from how I used to think about it when I still used web frameworks that prefer SQL. Especially when you start getting comfortable with data duplication, a lot of possibilities open up. A couple things I learned:
- Database schemas should not handle all of your appâs validation, mainly because of transactions (looking at you, Collection2)
- Denormalizing data is ok!
- You donât need an ORM
- For some apps Event Sourcing is the way to go
- Even though we use MongoDB: Make the schema explicit
I know that data duplication / denormalization feels really awkward for people coming from a SQL background; Which today is lots and lots of people. This is why you get the âWhere is the ORM in Meteor?â type questions. I found that as long as you make your schema explicit on the boundary between your domain and your storage, youâll be fine. This boundary could be made with the Domain Event pattern or with the Repository pattern, for example.
A simple example of the repository:
var PostsCollection = new Mongo.Collection('posts');
PostsRepository = {
findOne(id) {
return PostsCollection.findOne(id);
},
insert({title, author, content}) {
return PostsCollection.insert({title, author, content});
}
}
This allows you to encapsulate the details of object persistence, and so I know exactly where in the code the database schema is made explicit.
Things that bug me about persistence in Meteor:
First, I canât create the same collection in two places in my codebase, because Meteor will automatically try to create the /collection/insert
etc. methods, and considers this a conflict.
Second, publications only take cursors. This means I often need to write two difference âfindOneâ methods on my repository:
// lib
PostsRepository = {
publishOne(id) {
return PostsCollection.findOne(id);
},
findOne(id) {
return this.publishOne(id).fetch()[0];
},
}
// server
Meteor.publish('post', PostsRepository.publishOne);
// client
Template.blog.helpers({
post(id) { return PostsRepository.findOne(id); }
});
Not sure if itâs possible to fix this, obviously the client needs to know which collection to add the document toâŚ
Final thoughts
So ummm, concluding: I love Meteorâs low barrier to entry, and itâs brilliant pub/sub implementation. Iâd love to see that in becoming more mature, Meteor will continue to play to itâs strengths, as it did before with using Cordova and MongoDB with isomorphic JavaScript. Thanks for all the great work on such a fantastic platform!