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"