Yuurei (幽霊)
, (*1)
Atomicity in MongoDB explained by Xzibit, (*2)
What
It's a micro database layer with automatic mapping.
It is intended for advanced users of MongoDB
who know and understand the growth of a model on a schemaless database., (*3)
When I mean "micro", I mean the sum of NCLOC is less than the infamous
method UnitOfWork::createEntity of Doctrine 2, (*4)
Of course, features are also "micro". Don't expect the impossible. Nevertheless
there are some functionalities you don't find anywhere else., (*5)
Plus, this DBAL is fully extendable. My other repo DokudokiBundle for symfony2
adds 3 more mapping systems with minimum coding., (*6)
How
Use Composer like any other PHP package :, (*7)
$ composer.phar require trismegiste/yuurei dev-master
Why
Because, like the cake, "ODM is a lie". Turning MongoDB into an ORM-like
abstraction is the worst thing you can do against a NoSQL database., (*8)
With an ODM, you loose both NoSQL and RDBMS features, here are some :, (*9)
- No rich document of MongoDB because the query generator sux and the mapping is complex
- No schemaless capability because you freeze the model in classes
- No JOIN, you must rely on slow lazy loading
- No constraint of RDBMS (references and types) because there is none
- No atomicity : the only atomicity in MongoDB is on one document
In fact ODM is a slow ORM without [ACID][8] : what is the point of using MongoDB ?, (*10)
That's why I stop chasing the "Mythical Object Database" and start hacking., (*11)
Guidances
-
Rich documents by Hell ! You have atomicity.
- Stop to think 1 entity = 1 table
- Only a few root entities : 2, 3 or 4, 10 max for a full e-commerce app, not 200 !
- 1 app = 1 collection
- Forget 1NF, 2NF and 3NF. It's easier to deal with denormalized data in
MongoDB than to too many LEFT JOIN in MySQL
- Think like serialize/unserialize
- Don't try to reproduce a search engine with your database : use Elastic Search
- Don't try to store everything in collections : use XML files
So, you make a model divided in few parts without circular reference,
and you store it. It's like serialization but in MongoDB., (*12)
All non static properties are stored in a way you can query easily with the
powerfull (but very strange I admit) language of MongoDB., (*13)
See the PHPUnit tests for examples of the serialization process., (*14)
It's like serialization
You know PHP can save and restore any object in session with the serialization
and it doesn't need any mapping information, annotations nor repositories.
This was my paradigm when I started this library., (*15)
Why can't an ORM/ODM
do the same ? Well, of course I needed a NoSQL database. But this dbal/odm-like
does not need any mappping information and objects stored in the database
are keeping their original structures (without too much noise). So you can
make complex queries with MongoDb., (*16)
See full example in unit test, (*17)
// simple object
$doc = new \Some\Sample\Product('EF-85 L', 2000);
// persisting
$this->invocation->persist($doc);
// restoring with invocation repository
$restore = $this->invocation->findByPk((string) $doc->getId());
$this->assertInstanceOf('Some\Sample\Product', $restore);
// retrieving the content in the MongoDB
$dump = $this->collection->findOne(array('_id' => $doc->getId()));
$this->assertEquals('Some\Sample\Product', $dump['-fqcn']); // we store the FQCN
$this->assertEquals('EF-85 L', $dump['title']);
$this->assertEquals(2000, $dump['price']);
About MDE
With recent concepts of NoSQL, SOA and HMVC, I believe MDE is somewhat
past-history, not always but very often. In fact it's not the MDE itself
but a very common anti-pattern : the "Database Driven Development" where all
source code come from the database schema. Combined with CRUD generators, it
leads to anemic model, dumb constructors and useless setters/getters without
business meaning., (*18)
todo, (*19)
FAQ
What are the requirements ?
- PHP >= 5.4
- PECL Mongo extension >= 1.3
How to map properties ?
All object's properties are stored. You have only one thing to do :
The root classes must implement the Persistable interface
(there is a trait for implementing this interface). You don't need to extend
any particuliar class, therefore you can follow the DDD without constraint., (*20)
What is a "root class" ?
It is a class stored in the collection, which contains the MongoId in the key '_id'.
All other agregated objects in this class don't need to implement Persistable, they are
recursively stored., (*21)
What are the constraints ?
This library cannot save & restore private properties coming from a parent class.
Private properties in persisted classes are ok but they will not be seen by subclasses
and therefore cannot be persisted if you save these subclasses in the database.
This is a big limitations so be careful when you use the keyword 'private'., (*22)
How can I remove some transient properties ?
You can't. But you can have a transient class with the interface Skippable.
Use Decorator pattern or a State Pattern. Your model can do that., (*23)
Can I make some cleaning before persistence ?
Like serialization, you can implement Cleanable with 2 methods : wakeup and sleep, (*24)
How can I query for listing ?
Use the MongoCollection, you can't be more efficient than this low level layer, (*25)
How can I store pictures or PDF ?
Use a MongoBinData in your model, it is stored as is, (*26)
Can I use something else than MongoId for primary key ?
No. Of course MongoDB allow you to use something else but it is not a very
good idea for the most case., (*27)
What about MongoDate ?
Any DateTime are converted into MongoDate and vice versa., (*28)
What are the guidances you have used for this lib ?
- No mandatory inheritance for model except one interface (for DDD concern)
- Minimum number of switch because it is hidden inheritance
- No more than 5 methods per class
- No method longer than 20 NCLOC
- No static because it is global
- [SRP, OCP, LSP, ISP, DIP][9] at maximum level
- coupling at minimum level (checked with Mondrian )
Is there any lazy loading or proxy classes for DBRef ?
Fly, you fools, (*29)
What is the meaning of Yuurei ?
In fact this lib was part of DokudokiBundle and I wanted to keep the best
from this Bundle in a standalone library. So I kept
a japanese name for this. 幽霊 means ghost or spirit because in the original
bundle, this mapping system was named "Invocation"., (*30)