Persistence: Voyage, Glorp & GemStone

Fuel serializes arbitrary object graphs (including blocks and classes) very fast; Pharo uses it for debugging snapshots and code loading.

38.1 Options at a glance

Save the image Image file — fine for prototypes and personal tools

Fuel (binary, built in), STON (text) Document/object mapping MongoDB, in-memory (others via plugins) PostgreSQL, SQLite, others

P3 (PostgreSQL), SQLite3 binding, ODBC Native object database

Transactional, multi-user object repository

38.2 Fuel serialization

Fuel serializes arbitrary object graphs (including blocks and classes) very fast; Pharo uses it for debugging snapshots and code loading.

FLSerializer serialize: (BankAccount owner: 'Ada' balance: 100) toFileNamed: 'ada.fuel'.
FLMaterializer materializeFromFileNamed: 'ada.fuel'.       "→ a BankAccount(Ada: 100)"

38.3 Voyage

With MongoDB, query blocks are translated into Mongo queries (or pass a dictionary: BankAccount selectMany: { 'owner' -> 'Ada' } asDictionary). Class-side methods with the <voyageDescription> (older: <mongoDescription>) pragma customize how attributes are stored and referenced.

"Mark root classes"
BankAccount class >> isVoyageRoot
    ^ true

"Choose a repository"
| repo |
repo := VOMemoryRepository new.                                   "for tests"
"repo := VOMongoRepository host: 'localhost' database: 'bank'."   "MongoDB"
repo enableSingleton.

(BankAccount owner: 'Ada' balance: 100) save.
(BankAccount owner: 'Bob' balance: 20) save.
BankAccount selectAll.
BankAccount selectOne: [ :each | each owner = 'Ada' ].
BankAccount selectMany: [ :each | each balance > 50 ].
(BankAccount selectOne: [ :each | each owner = 'Bob' ]) remove.

38.4 Glorp

Glorp maps objects to tables via a descriptor system:

Glorp supports one-to-one, one-to-many, many-to-many mappings, embedded values, inheritance strategies, and lazy proxies.

DescriptorSystem << #BankDescriptorSystem slots: {}; package: 'Bank-Glorp'.

BankDescriptorSystem >> allTableNames
    ^ #( 'ACCOUNTS' )

BankDescriptorSystem >> tableForACCOUNTS: aTable
    (aTable createFieldNamed: 'id' type: platform serial) bePrimaryKey.
    aTable createFieldNamed: 'owner' type: (platform varChar: 100).
    aTable createFieldNamed: 'balance' type: platform integer

BankDescriptorSystem >> classModelForBankAccount: aClassModel
    aClassModel
        newAttributeNamed: #id;
        newAttributeNamed: #owner;
        newAttributeNamed: #balance

BankDescriptorSystem >> descriptorForBankAccount: aDescriptor
    | table |
    table := self tableNamed: 'ACCOUNTS'.
    aDescriptor table: table.
    (aDescriptor newMapping: DirectMapping) from: #id to: (table fieldNamed: 'id').
    (aDescriptor newMapping: DirectMapping) from: #owner to: (table fieldNamed: 'owner').
    (aDescriptor newMapping: DirectMapping) from: #balance to: (table fieldNamed: 'balance')
| session |
session := BankDescriptorSystem sessionForLogin: aLogin.     "a Login with platform, host, database, user"
session login.
session inUnitOfWorkDo: [ session register: (BankAccount owner: 'Ada' balance: 100) ].
session read: BankAccount where: [ :each | each balance > 50 ].
session readOneOf: BankAccount where: [ :each | each owner = 'Ada' ].

38.5 Direct SQL

(P3 for PostgreSQL: (P3Client url: 'psql://user:secret@localhost/bank') query: 'SELECT 1'.)

| db |
db := SQLite3Connection openOn: 'bank.db'.
db execute: 'CREATE TABLE IF NOT EXISTS accounts (owner TEXT, balance INTEGER)'.
db execute: 'INSERT INTO accounts VALUES (?, ?)' with: #( 'Ada' 100 ).
(db execute: 'SELECT * FROM accounts') rows collect: [ :row | row at: 'owner' ].
db close.

38.6 GemStone/S: Smalltalk as a database

GemStone/S 64 (GemTalk Systems) is a Smalltalk server where every committed object is persistent and shared. Many sessions (*Gems*) run in parallel against one repository managed by the *Stone*, with ACID transactions and a shared page cache. Objects reachable from a persistent root are stored automatically — no mapping layer:

Typical stack: develop in Pharo, deploy domain code + Seaside or a REST API to GemStone (GsDevKit, Rowan for code management, topaz for command-line scripting). GemStone handles terabyte-scale object repositories in finance, logistics, and shipping.

"In a GemStone session (Topaz, Jade, or GemStone-Pharo tools)"
UserGlobals at: #Accounts put: OrderedCollection new.
(UserGlobals at: #Accounts) add: (BankAccount owner: 'Ada' balance: 100).
System commitTransaction.          "now visible to every other session"
System abortTransaction.           "refresh your view / discard changes"