Obscenely simple object persistence
Simple
Low maintenance for real. No instance variables, no getters, and no setters — just saved models with your data.
Add data to a model →Composable
Do you need to compose models? But of course you do! No mapping required — just add those (sub)models, save its children first, and you're good to go.
Compose models →Portable
Shared API across backends. For example, start with SQLite and easily move to MongoDB or PostgreSQL while caching with Redis.
Data portability →Saving Smalltalk objects in seconds
Install in Pharo
Need to persist modeled data?
This is what happens when you do it with Mapless:
- Create a dedicated subclass for each type of data model.
Know in advance the attributes they'll require.Create instance variables for these attributes.Add accessors for each of them.Careful map them so it all fits in the database.Patiently re-map them every time you need to change its design saying 'hi' to object-database mismatches.- Actually save them.
- Profit.
How does it look like?
Let's say you want to store and retrieve instances of Task
as Mapless models using MongoDB as backend.
In essence, you'll create a repository object to handle Task
instances — saving, querying, updating, or deleting them.
Let's see the snippets for achieving these basic operations one by one.
Create the repository
There are two ways to access MongoDB with Mapless. MaplessStandaloneMongoPool
for single server setups and MaplessMongoReplicaSetPool
for clusters of replica sets.
The following one shows an example for accessing a local standalone MongoDB.
Create a repository to access a standalone MongoDB service
Model your data
Creating new instances of your data models is straightforward — treat them like any other object.
The only thing to remark here is that you do not need to create the mutator methods description:
nor isComplete:
in the Task
class.
Create a new model
Storing
To store data, simply send the save
message to any Mapless
instance, and be automatically inserted or updated in the database.
The repository object features the do:
method, accepting a block closure. This allows your Mapless object to understand the repository it should work with.
Alternatively, you can implement Task class>>getRepository
to have the model provide the repository directly, enabling it to handle the save
operation without relying on a closure from external sources.
Saving a model
Querying
Mapless supports various persistent backends, and the method of implementing queries for your data depends on the specific backend in use. For instance, with a PostgreSQL or SQLite backend, SQL queries are employed. In the case of MongoDB, Mapless utilizes the MongoTalk querying feature, while Redis adopts a namespaced convention.
Fetching objects
Making the queries powerful
Let's say you are using a MongoDB backend and want a production ready query by username
for this Task
. All you have to do is to create an index in MongoDB for username
in the Task
collection. Optionally, you can add a class method in Task
tailored to this query.
Querying efficiently
Adding properties to models
Simply do it. Unless overriden, Mapless catches all messages you send to a model via DNU. It is smart enough to allow you to add a date a string or almost any other object without declaring instVars or demanding making schemas or any accessors for it.
Adding information to your data
Composition
When you model data using your custom Mapless classes, you have the flexibility to add almost any property to them, especially other Mapless models—hence, composition. In such cases, Mapless efficiently organizes the data, utilizing one collection or table in the backend per type of Mapless object.
The main rule when working with composition here is save the children first.
Composing models
Graph navigation
Saving a Mapless object is akin to planting the root of a graph. When interacting with a composed Mapless object, accessing the child Mapless feels like navigating one level deeper in the object graph. Composed Mapless objects return lazy references for every known (sub)Mapless to the parent. When you send a message to a (sub)model, the DNU method seamlessly resolves it, making it appear as if it was always there.
At the same time, if a Mapless object never sends any message to a child, then your application is not doing any unnecessary query. This prevents your backend from doing unnecessary extra work in advance making your system more efficient by default and in general.
Navigating model graphs
Supported backends
All Mapless subclasses share the same fundamental Smalltalk API. When resolving requested operations, they delegate to a corresponding MaplessRepository
subclass aligned with one of the concrete supported backends.
Currently supported backends include:
- SQLite.
- PostgreSQL.
- MongoDB.
- Redis.
- Memory.
UnQLite(Deprecated and retiring soon).
Why Mapless?
I wanted to persist objects with low friction, low maintenance but high scale, throughput and availability capabilities and Mapless is totally biased towards that. This framework is what I came up with after incorporating my SaaS experience with airflowing.
- There are no instVars...
- No accessors...
- No object-mapping impedance...
- — Only persistence.
- What would you do with it?
Applicability and Real-World Scenarios
Mapless, in active production since 2014, has proven its versatility across diverse scenarios—from initial Proof of Concepts to supporting mission-critical services actively monitored 24/7 by numerous network operators in the telecom industry.
Mapless excels in various use cases, including:
- Proof of Concept APIs: Rapidly develop and test APIs for conceptual exploration.
- BBFs - Backend For Frontends: Serve as a robust backend solution tailored for frontend applications.
- MVP Development: Facilitate the creation of Minimal Viable Products (MVPs) with minimal costs and unparalleled speed.
- RESTful Custom APIs: Craft customized and efficient APIs adhering to RESTful principles.
- Web Application and API Integration: Seamlessly integrate with external CRMs, facilitating lead generation for the hospitality industry in Austria.
- Scalable APIs and networking microservices: Scaling APIs to thousands of operations per second for years in the North American Telecommunications industry.
Guides
Here are some guides to help you get started:
- Recommended workflow
- Installing Pharo
- RESTful APIs with Mapless and Teapot.
Redis as a cache.To-Do- The Observer Pattern beyond one Smalltalk image.
Using Maples with a MongoDB Replica Set.To-DoSimple Web Application PoC with htmx Teapot and Mapless.To-DoHow-to use Maples with SQLite in production with litestream.io backups. To-Do
Frequently Asked Questions
What saving 'Models' means? why not any object?
By "Models," Mapless refers to instances that represent persistent data, excluding transient entities such as contexts, sockets, or file handlers. Any instance of a Mapless subclass can be saved effortlessly. These instances are serialized and stored as documents in their respective MongoDB collection or PostgreSQL table.
Is only for tree-like structures or does actually support an object graph?
As long as you adhere to its rules, such as saving children first and treating your models as NoSQL-friendly documents and not trying to store a Socket or some unserializable object, yes, Mapless does support an arbitrary object graph.
Why would I want to use Mapless?
Because you might want to profit from some of these benefits:
- JSON friendly persisted models.
- Having frictionless system interoperability with Ruby and NodeJS and any JSON friendly object oriented app.
- Dealing with Gigas or Teras order of magnitude databases.
- High availability via MongoDB Replica Set features or PostgreSQL Replication.
- Replication of the whole database across the cloud.
- World class databases many engineers are familiar to use and maintain.
- Powerful queries and custom indices.
- Freedom from a prioristic instVars declarations.
- Freedom from mappings maintenance when the design changes.
- Cross-image model caching (requires Redis).
- Cross-image model observation/reaction, horizontally scalling the Observer Pattern (requires Redis PUB/SUB).
Found a Bug?
Need that feature for your use case?
Please create a GitHub issue in the Mapless repository.
Need additional support?
Don't hesitate to reach out in 𝕏 or LinkedIn Pharo Discord server for discussing design possibilities and implementation support.
License
Since 2014 Sebastian Sastre published Mapless under the terms of MIT License.