PackageDescription: MongoObjectRepository


Mongo Object Repository

Last published: September 17, 2021 by 'nross'

Defines 4 Classes
Extends 18 Classes


This is an example repository and has tests to show MongoDB use. Smalltalk objects are converted to MongoDB documents, stored in a MongoDB server and fetched from it.

This package demonstrates a simple object store using MongoDB. MongoDB is a "document store" where tables are refered to as "collections", and rows are refered to as "documents". A MongoDB row (document, that is) is just a dictionary of keys and values, whose keys are always strings. In this package, Smalltalk instances are kept in a dictionaries and inserted or recalled from MongoDB collections.

A Smalltalk class instance is converted to a dictionary holding its instance variable (ivar) names as keys, and its ivar contents as values. Ivar values that correspond to Mongo basic types are inserted as is. The remaining instance variables must be converted to "documents" in their own right, and so on down to their primitive objects, those which MongoDB accepts as basic types.

A Smalltalk instance can be viewed as an object graph connecting all its ivars, with each ivar a subgraph, and so on, posibly containing numerous cycles. To avoid infinite cycles, objects are stored just once, and their reference number is substituted thereafter. However, there is curious problem when converting an object to a heirarchy of dictionaries, of dictionaries. The entire heirarchy of dictionaries could be considered a single MongoDB document, since it is after all just a dictionary. But that's not usually desirable in an object store. Imagine an entire linked list being held in a single row!

The approach taken here is to allocate one MongoDB "collection" (ie., table) for each Smalltalk domain class, so that each instance of a class is a discrete document (ie., row). A main coordinator class, MongoIDTracker does the work, and must be told which Smalltalk classes should be distinct documents. For example,
MongoIDTracker initializeForDomainClasses: #(City)
will record all instances of City as separate documents in a collection call 'city' in the example below (from MongoObjectsTest>>testSaveAndRestoreIdentityPreservation

cities := self citiesGraph. "A collection of City objects."
MongoIDTracker initializeForDomainClasses: #(City). "Prepare to store City instances."
MongoIDTracker registerDomainObjectsReachableFrom: cities. "Register all our instances (give them IDs)"
allReached := MongoIDTracker registeredObjectsForClass: City. "Return all of our registered cities."
encoded := allReached collect: [:each| each asMongoRow]. "Convert each city to a MongoDB document."
collection := database addCollection: 'cities'. "CREATE a collection named 'cities' to database."
collection addAll: encoded. "INSERT the cities into the database."
results := collection query: Dictionary new. "SELECT all documents from 'cities'."
self assert: results size = cities size. "Test for integrity."
self assert: ( results first at: 'className') = 'City'.
decoded := results collect:[:each| each asObjectFromMongoDocument]. "Convert documents to City instances."

In the above example, the MongoIDTracker tracks a single class, City. Another test illustrates more than one:
MongoIDTracker initializeForDomainClasses: #(Depth8Image MappedPalette).

This package is able to automatically store and recall instances of a many Smalltalk classes, but some other classes may need special method implementations. For example, a hybrid object that is indexed, but has additional data. See the methods:
asMongoRow
asMongoDocument
asObjectFromMongoDocument
asObjectFromMongoDocument:

and implement them for any classes that need special handling.