From 76436306b8c7d28eee31484f79bae555b557a5c9 Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Wed, 6 Apr 2022 23:52:35 -0300 Subject: [PATCH 1/7] readme edit --- LICENSE | 9 ++ README.md | 289 ++++++++++++++++++++------------------------------- changelog.md | 3 +- 3 files changed, 125 insertions(+), 176 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ca43b724 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2014 Sebastian Sastre + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index d3059365..2d0d9581 100644 --- a/README.md +++ b/README.md @@ -1,177 +1,116 @@ -Mapless -======= - -*Obscenely simple persistence for Smalltalk.* Mapless is a small framework for storing objects in a key->value fashion (i.e.: noSQL databases) without requiring any kind of mapping definitions. - -Currently supported backends are `MongoDB` and `Redis`. - -**Current best version tagged as:** v0.5.1-alpha - -## Loading - -By default `Mapless` will load `Memory`, `Mongo` and `Redis` backends: +# Mapless + +Multi-backend schema-less persistence for Smalltalk. + +

+ + + + + + +

+ +___ + +## Mapless most **important features** are: + +- Intuitive API for frictionless persistence. +- No need to create and maintain schemas. +- Composable. +- JSON friendly. +- No need to create accessors and mutators. +- Multiple backends to chose from. +- Enables smooth data migration/interoperation among backends. +- Via Redis PUB/SUB, scalable observer-pattern functionality across images. + +## Supported backends +1. MongoDB +2. Redis +3. Memory +4. PostgreSQL +5. UnQLite + +## Examples + +### Creating repositories + +```Smalltalk +"MongoDB standalone" +mongoRepository := MaplessMongoRepository + for: 'Mapless-Test' + with: MaplessStandaloneMongoPool local. +``` + +```Smalltalk +"MongoDB Replica Set" +databaseName := 'Mapless-Test'. +mongoRepository := MaplessMongoRepository + for: databaseName + with: (MaplessMongoReplicaSetPool mongoUrls: { + 'localhost:27017'. + 'localhost:27019' + } + database: databaseName) +``` + +```Smalltalk +"Redis" +"Since Redis doesn't use database names, +we use one of its 0-15 index." +databaseIndex := 3. +accessor := MaplessRedisPool local. +accessor start. +accessor auth: 'my_password'. +redisRepository := MaplessRedisRepository + for: databaseIndex + with: accessor + using: MaplessTrivialResolver new +``` +```Smalltalk +"UnQLite" +dbFilename := FileSystem workingDirectory / 'Mapless-Tests.db'. +unqliteRepository := MaplessUnQLiteRepository for: dbFilename pathString +``` + +### Saving and loading a mapless object + +```Smalltalk +"Instanciates a mapless object." +guy := DummyPerson new + firstName: 'Aristotle'; + yourself. + +"Saves it." +repository save: guy. + +"Loads one by known ID." +identified := repository findOne: DummyPerson atId: guy id. + +"Loads all instances of that class that were stored in that database." +all := repository findAll: DummyPerson. + +"Query to load all the instances that match the condition (or receive an empty collection)." +some := repository findAll: DummyPerson where: [ :each | lastName = 'Peterson' ]. + +"Conditionally loading one instance (or nil)." +one := repository findOne: DummyPerson where: [ :each | lastName = 'Peterson' ]. +``` + +## Installation + +Open a Pharo workspace and evaluate: Metacello new - baseline: 'Mapless'; - repository: 'github://sebastianconcept/Mapless:master/src'; - load. - -## Design philosophy -Without loosing pragmatism, here is a notion used as source of inspiration for Mapless' design: - - *There are no instVars...* - *There are no accessors...* - *There is no object-mapping impedance...* - - *Only persistence.* - -### Applicability - -Mapless can be used for Model persistence, object oriented shared cache, observer pattern across images and network JSON communication. - -### Motivation -> "I wanted to persist objects with *low friction* and *low maintenance* but *high scaling* and *availability* capabilities so Mapless is totally biased towards that. This framework is what I came up with after incorporating my experience with [Aggregate](https://github.com/sebastianconcept/Aggregate) in [airflowing](http://airflowing.com)." ~ [Sebastian Sastre](http://sebastiansastre.co) - - - -### How does it look? - -You can store and retrieve your Mapless models on MongoDB like a breeze. -#### Getting the connection - - odb := MongoPool instance databaseAt: 'YourMongoDatabase'. - -#### Create a new model - -Just subclass MaplessModel with your app "business objects". Here we use a Task model: - - task := Task new description: 'Try Mapless'; beIncomplete. - -#### Save it - -In Mapless you do things sending a #do: with a closure which Mapless uses to automatically discern which MongoDB database and collection has to be used. It also will know if it needs to do an insert or an update. As a bonus, you get the collection *and* the database created lazily. - -Want to save something? Zero bureaucracy, just tell that to the model: - odb do:[ task save ]. - -The spirit of the project is to preserve developer *low-friction* when using persistence. -#### Fetching data - -Getting all models of a given class: - odb do:[ Task findAll ]. - -Getting a specific model: - odb do: [ Task findId: 'c472099f-79b9-8ea2-d61f-e5df34b3ed06' ]. - -For getting efficiently a (sub)group of models, you write your own Mapless class side getters. They should act on the MongoDB indices with the parameters of your query. You'll get your models in a breeze: - - odb do:[ Task findAtUsername: 'awesomeguy' ]. - -#### Adding something to a model - -You can use Mapless models pretending they are [prototypical](http://en.wikipedia.org/wiki/Prototype-based_programming) like in [Self](http://en.wikipedia.org/wiki/Self_(programming_language)) or [Javascript](http://en.wikipedia.org/wiki/JavaScript) objects. No instVars. No setters. No getters. All just works: - - odb do: [ - task - deadline: Date tomorrow; - notes: 'Wonder how it feels to use this thing, hmm...'; - save ]. - -#### Need composition? - -Of course you do! The only thing you need is to save the children first - odb do: [ | anAlert | - anAlert := Alert new - duration: 24 hours; - message: 'You will miss the target!'; - save. - task - alert: anAlert; - save ] - -This is what I like to call *low-maintenance*. -#### Navigating the object graph - -Mapless embraces an aggressive *lazy approach*. When you query for models you get them. But if they are composed by other (sub)Mapless, they are going to be references and *only* reify into the proper Mapless model *if* you send them a message and you can do this with arbitrary depth: - - odb do: [ task alert message ]. - - odb do: [ task alert class = MaplessReference ]. "<- true" - - odb do: [ task alert description ]. "<- 'You will miss the target!'" - -#### Persisting different models - -Say for your app you now need to store different kind of models, say `List` or `User` or anything else, how you typically proceed? - -This is what happens to you with Mapless: - -1. Create a subclass for them. -2. Know what attributes they will need in advance. -3. Create its attributes' instVars. -4. Make accessors for them. -5. Elegantly map their type for each property so it all fits in the database. -6. Patiently re-map them every time you need to change its design. -7. Patiently migrate when you need production data to adopt the new design. -8. Profit. - -### How does it look? on Redis (this is work in prgress) - -Using Redis supported Mapless models are interesting for: - -1. **Caching**. It will hold the data in RAM so you get great response times. -2. **Shared caching**. If your app scales load horizontally, you can use Mapless on Redis as a cache shared across your service images. -3. **Reactivity**. Mapless uses Redis [PUB/SUB](http://redis.io/topics/pubsub) feature to observe/react models among N Pharo worker images enabling to use the [Observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) with [horizontal scaling](http://en.wikipedia.org/wiki/Scalability#Horizontal_and_vertical_scaling). - -Here is a workspace for Redis based Mapless models: - - redis := RedisPool instance. - guy := DummyPerson new - firstName: 'John'; - lastName: 'Q'; - yourself. - redis do: [ :c | guy save]. - redis do: [ :c | DummyPerson findAt: '38skolpqv0y9lazmk772hmsed' ]. - redis do: [ :c | (DummyPerson findAt: '38skolpqv0y9lazmk772hmsed') lastName] - -### State - -This project code is considered beta. Check tests and please contribute! - -### Contributions - -...are *very* welcomed, send that push request and hopefully we can review it together. - -### Direction? - -1. We would love to see more unit tests and iterate the reactive features so you can ultimately get an a model in one image being observed in regard to one event by something in another image and that something reacting upon that event. Broadcast and multicast of events would be also a nice feat and not too hard to do. Have design suggestions? Let's have a chat! (find me in Pharo's Discord server) -2. For MongoDB-based Mapless, a nice feature would be to have UserModel ensureIndex: '{ key1: 1, key2: -1 }' - -### *Pharo Smalltalk -Getting a fresh Pharo Smalltalk image and its virtual machine is as easy as running in your terminal: - - wget -O- https://get.pharo.org | bash - -_______ - -MIT License - -Copyright (c) 2014 [Sebastian Sastre](http://sebastiansastre.co) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + baseline: 'Mapless'; + repository: 'github://sebastianconcept/Mapless:v0.5.0-alpha/src'; + load + +## Include as dependency +Add it like this your own project's BaselineOf or ConfigurationOf + + spec + baseline: 'Mapless' + with: [ spec + repository: 'github://sebastianconcept/Mapless:v0.5.0-alpha/src'; + loads: #('Core' 'Mongo' 'Memory' 'Redis' 'UnQLite') ] \ No newline at end of file diff --git a/changelog.md b/changelog.md index b4d5c473..b5078dfe 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ April 6, 2022 =================================== -* Removed the docker directory with a replica set docker-compose.yml. This is better done when using your own or https://github.com/sebastianconcept/mongo-rs +* Removed the docker directory with a replica set docker-compose.yml. This is better done when using your own or https://github.com/sebastianconcept/mongo-rs. +* Starting to improve README based on https://github.com/sebastianconcept/Mapless/issues/87. April 3, 2022 =================================== From 141368da5b21ff2d9eb2c1e626d7766f8a78ba5e Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Wed, 6 Apr 2022 23:54:49 -0300 Subject: [PATCH 2/7] edit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d0d9581..05b5baa4 100644 --- a/README.md +++ b/README.md @@ -91,10 +91,10 @@ identified := repository findOne: DummyPerson atId: guy id. all := repository findAll: DummyPerson. "Query to load all the instances that match the condition (or receive an empty collection)." -some := repository findAll: DummyPerson where: [ :each | lastName = 'Peterson' ]. +some := repository findAll: DummyPerson where: [ :each | each lastName = 'Peterson' ]. "Conditionally loading one instance (or nil)." -one := repository findOne: DummyPerson where: [ :each | lastName = 'Peterson' ]. +one := repository findOne: DummyPerson where: [ :each | each lastName = 'Peterson' ]. ``` ## Installation From f71a9c94807f5196492704e224d626673da29b91 Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Wed, 6 Apr 2022 23:57:23 -0300 Subject: [PATCH 3/7] more expressive var names --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 05b5baa4..d6259ac9 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,13 @@ repository save: guy. identified := repository findOne: DummyPerson atId: guy id. "Loads all instances of that class that were stored in that database." -all := repository findAll: DummyPerson. +allOrEmpty := repository findAll: DummyPerson. -"Query to load all the instances that match the condition (or receive an empty collection)." -some := repository findAll: DummyPerson where: [ :each | each lastName = 'Peterson' ]. +"Query to load all the instances that match the condition." +someOrEmpty := repository findAll: DummyPerson where: [ :each | each lastName = 'Peterson' ]. -"Conditionally loading one instance (or nil)." -one := repository findOne: DummyPerson where: [ :each | each lastName = 'Peterson' ]. +"Conditionally loading the first matching instance." +oneOrNil := repository findOne: DummyPerson where: [ :each | each lastName = 'Peterson' ]. ``` ## Installation From fc3d152a8dcd243b25f46540bb009c31746aad64 Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Wed, 6 Apr 2022 23:59:08 -0300 Subject: [PATCH 4/7] wasn't merely a guy so... --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d6259ac9..aa9da400 100644 --- a/README.md +++ b/README.md @@ -77,15 +77,15 @@ unqliteRepository := MaplessUnQLiteRepository for: dbFilename pathString ```Smalltalk "Instanciates a mapless object." -guy := DummyPerson new +genius := DummyPerson new firstName: 'Aristotle'; yourself. "Saves it." -repository save: guy. +repository save: genius. "Loads one by known ID." -identified := repository findOne: DummyPerson atId: guy id. +identified := repository findOne: DummyPerson atId: genius id. "Loads all instances of that class that were stored in that database." allOrEmpty := repository findAll: DummyPerson. From 2ab105f03d7250f459689af036970b7720147a3d Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Thu, 7 Apr 2022 00:06:45 -0300 Subject: [PATCH 5/7] adds tests badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aa9da400..2317ef0d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Multi-backend schema-less persistence for Smalltalk.

+ From 1352d6b7e0dc5e922f6a866cd86256990a3935ad Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Thu, 7 Apr 2022 13:14:38 -0300 Subject: [PATCH 6/7] readme edit --- README.md | 55 ++++++++++--------------------------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 2317ef0d..bf0cd61e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mapless -Multi-backend schema-less persistence for Smalltalk. +Schema-less persistence for Smalltalk with support for multiple backends.

@@ -14,8 +14,6 @@ Multi-backend schema-less persistence for Smalltalk. ___ -## Mapless most **important features** are: - - Intuitive API for frictionless persistence. - No need to create and maintain schemas. - Composable. @@ -34,47 +32,6 @@ ___ ## Examples -### Creating repositories - -```Smalltalk -"MongoDB standalone" -mongoRepository := MaplessMongoRepository - for: 'Mapless-Test' - with: MaplessStandaloneMongoPool local. -``` - -```Smalltalk -"MongoDB Replica Set" -databaseName := 'Mapless-Test'. -mongoRepository := MaplessMongoRepository - for: databaseName - with: (MaplessMongoReplicaSetPool mongoUrls: { - 'localhost:27017'. - 'localhost:27019' - } - database: databaseName) -``` - -```Smalltalk -"Redis" -"Since Redis doesn't use database names, -we use one of its 0-15 index." -databaseIndex := 3. -accessor := MaplessRedisPool local. -accessor start. -accessor auth: 'my_password'. -redisRepository := MaplessRedisRepository - for: databaseIndex - with: accessor - using: MaplessTrivialResolver new -``` -```Smalltalk -"UnQLite" -dbFilename := FileSystem workingDirectory / 'Mapless-Tests.db'. -unqliteRepository := MaplessUnQLiteRepository for: dbFilename pathString -``` - -### Saving and loading a mapless object ```Smalltalk "Instanciates a mapless object." @@ -84,16 +41,24 @@ genius := DummyPerson new "Saves it." repository save: genius. +``` +```Smalltalk "Loads one by known ID." identified := repository findOne: DummyPerson atId: genius id. +``` +```Smalltalk "Loads all instances of that class that were stored in that database." allOrEmpty := repository findAll: DummyPerson. +``` +```Smalltalk "Query to load all the instances that match the condition." someOrEmpty := repository findAll: DummyPerson where: [ :each | each lastName = 'Peterson' ]. +``` +```Smalltalk "Conditionally loading the first matching instance." oneOrNil := repository findOne: DummyPerson where: [ :each | each lastName = 'Peterson' ]. ``` @@ -108,7 +73,7 @@ Open a Pharo workspace and evaluate: load ## Include as dependency -Add it like this your own project's BaselineOf or ConfigurationOf +In BaselineOf or ConfigurationOf it can be added in this way: spec baseline: 'Mapless' From e2519b547f789e993da250ea344001917d1a7fd7 Mon Sep 17 00:00:00 2001 From: Sebastian Sastre Date: Thu, 7 Apr 2022 13:38:53 -0300 Subject: [PATCH 7/7] adds ambition section --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bf0cd61e..cddb35ee 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Schema-less persistence for Smalltalk with support for multiple backends. ___ +## Features - Intuitive API for frictionless persistence. - No need to create and maintain schemas. - Composable. @@ -23,6 +24,10 @@ ___ - Enables smooth data migration/interoperation among backends. - Via Redis PUB/SUB, scalable observer-pattern functionality across images. +## Ambition + +Mapless gives you performant state plasticity and high availability in a scale that goes beyond one Smalltalk image and without backend vendor locking nor object-mapping impedance mismatch. + ## Supported backends 1. MongoDB 2. Redis @@ -79,4 +84,5 @@ In BaselineOf or ConfigurationOf it can be added in this way: baseline: 'Mapless' with: [ spec repository: 'github://sebastianconcept/Mapless:v0.5.0-alpha/src'; - loads: #('Core' 'Mongo' 'Memory' 'Redis' 'UnQLite') ] \ No newline at end of file + loads: #('Core' 'Mongo' 'Memory' 'Redis' 'UnQLite') ] +