Mongo/Voyage singleton vs instance mode and seaside Request filters

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Mongo/Voyage singleton vs instance mode and seaside Request filters

Sabine Manaa
Hi,

the last days we had a an interesting discussion on slack.
Stephan Eggermont asked me to post a summary here for discussion, Information and because of the 10.000 messages limitation of slack:

Situation:
Til now I used Mongo/Voyage in singleton mode [1]. That means in short that I had one single database/repository in mongo. I then decided that I want to have one database/repository per customer (one customer has n persons) and one "super-database/repository" for data like currencies valid for all customers and for lookup of the right database for each user/email address. This has a lot of advantages. Mongo allows to have n databases/repositories within one installation.

First, I thought, that I have to change my code from

"aPersonInstance save"
(this is the singleton mode way, always same repository)

to "aCustomerRepository save: aPersonInstance"
(instance mode - so that mongo knows which repository to use)

Then Esteban proposed to use a SeasideFilter. This would be a lot better.
He told me to create a subclass of WARequest Filter and implement:

"handleFiltered: aRequestContext
    VOCurrentRepository
        value: self obtainSessionRepository
        during: [  self next handleFiltered:  aRequestContext]

obtainSessionRepository
    ^ self session
    propertyAt: #repository
    ifAbsentPut: [ self newRepositoryForUser ]

newRepositoryForUser
   ^  VOMongoRepository database: 'client-database'"

He also implemented and released some more code for this (e.g. VOCurrentRepository) [2].
The idea/concept is, that seaside uses the right repository within the current request context  and one does not have to deal with the  repositories.

Udo Schneider suggested the DynamicVariable subclass and get it within the WACurrentRequestContext

I did not know/use about Seaside Filters and not about DynamicVariables, so it was very interesting.

Currently I think that perhaps for me overwriting the save method (I have a superclass of all my voyage root objects) would be sufficient for me. My customers repository is set at login and kept in the session.

RKAObject>>save
    | theRepository |
    theRepository := [ WACurrentRequestContext value session customerRepository ]
        on: WARequestContextNotFound
        do: [ :ex | nil ].
    theRepository save: self.

But I am not finished with this and opinions are welcome and perhaps this is useful for others.
 
There was a lot more discussion. I will put the whole discussion from slack in an answer of this post. So it is kept but not attached in answers to THIS post.

Sabine


 [1] http://files.pharo.org/books-pdfs/entreprise-pharo/2016-10-06-EnterprisePharo.pdf chapter on voyage
[2]https://github.com/pharo-nosql/voyage/commit/d1fa97e41470671452d3501421a7bd0ac60456e9
Reply | Threaded
Open this post in threaded view
|

Re: Mongo/Voyage singleton vs instance mode and seaside Request filters

Sabine Manaa
This is the slack discussion. 
Please answer to the first mail and not to this one. I hope this is ok, i put it here for that it is not lost after 10.000 messages...

Sabine Manaa [10:34 AM] 
I want to run mongo voyage in *instance* mode (each of my customers will have its own repository). How can I tell voyage to save my object in a certain repository (= mongo database)?
In the enterprise pharo book, it is written that "save" writes in the singleton mode directory (this works fine, e.g. `RKAPerson new save`).
But for using a certain repository it should be `RKAPerson new save:` acccording to the book.  
I dont get it how to do this, to me it seems that save: implementation in Object is missing. @udos  or @norbert.hartl  or @estebanlm  can you tell me how to do this?

Esteban Lorenzano [10:35 AM] 
you cannot use it as is

[10:35]  
you will need to create something particular

[10:35]  
I implemented something like that some time ago

[10:35]  
idea was to write a seaside filter

[10:35]  
then in the filter simething like:

[10:36]  
 ```self 
    useRepository: self repositoryForClient
    during: [ self handleNext: aRequest ]```

[10:37]  
and you can implement:

[10:38]  
 ```repositoryForClient
    ^ self session propertyAt: #repository ifAbsentPut: [ VOMongoRepository database: 'client-database' ]```
(edited)

[10:38]  
then

[10:40]  
 ```useRepository: aRepository during: aBlock 
    | oldRepository |
   oldRepository := VORepository current. 
   VORepository setRepository: aRepository.
   aBlock ensure: [ VORepository setRepository: oldRepository ]```

[10:40]  
voilà, that will work

[10:41]  
(of course, is more or less like that, not exactly… but you get the idea)

Sabine Manaa [10:42 AM] 
Hi Esteban, thanks for answering so fast, it is very good for me so I can proceed with my work. I understand that I would keep the repository of the user in the session and each time i do a database operation, the repository will be changed.

I ask myself if this will lead to problems/if this will work if there are many users... I can not imagine that for each operation switching the repository is the right way?

Esteban Lorenzano [10:43 AM] 
it will work because each request is handled separately

Sabine Manaa [10:43 AM] 
So would you recommend, *not* to create a repository for each of my users? (One user is a *company* with N persons)

Esteban Lorenzano [10:43 AM] 
no, is ok

[10:43]  
is a valid approach

[10:43]  
now

Sabine Manaa [10:43 AM] 
ok. I will try it! Thanks a lot! btw: the book is wrong at this place.

Esteban Lorenzano [10:44 AM] 
if you have a company with +1 users

[10:44]  
and you want a database for each company

Sabine Manaa [10:44 AM] 
yes, i think there are a lot of advantages in the future when I do it like this

Esteban Lorenzano [10:44 AM] 
then you could want to keep your repositories other place than session

[10:44]  
but that’s another story

[10:45]  
for start, that's ok :slightly_smiling_face:

[10:45]  
and the problem with this

Sabine Manaa [10:45 AM] 
i dont understand „keep your repositories other place than session“ -> repository is in database, session in my image

Esteban Lorenzano [10:45 AM] 
is you need the relation user-database somewhere

Sabine Manaa [10:46 AM] 
i keep it in a „super-database“ for lookup

Esteban Lorenzano [10:46 AM] 
ok :slightly_smiling_face:

Sabine Manaa [10:46 AM] 
ok, I will try it and come back if it does not work, thanks again!

Esteban Lorenzano [10:47 AM] 
welcome

Sabine Manaa [10:47 AM] 
do you want me to put the book.is-wrong- information somewhere? for that it will be changed?

Esteban Lorenzano [11:04 AM] 
oops

[11:04]  
I just remember that you need to do *another step* to make that work :stuck_out_tongue:

[11:05]  
and what part of the book does not work?

Sabine Manaa [11:05 AM] 
page 156: „Second is Instance mode. In Instance mode, the first argument is always the repository on which to perform the operation.
Persisting Objects with Voyage
save:
remove:
removeAll:
selectAll:
selectOne:where:
selectMany:where:
stores an object into repository (insert or update) removes an object from repository
removes all objects of class from repository retrieves all objects of some kind
retrieves first object that matches the where clause retrieves all objects that matches the where clause"

Esteban Lorenzano [11:06 AM] 
what’s bad with that?

Sabine Manaa [11:07 AM] 
there is no save: in Object.

[11:07]  
this methods are missing.

Esteban Lorenzano [11:07 AM] 
it is correct

Sabine Manaa [11:07 AM] 
that was my question

Esteban Lorenzano [11:07 AM] 
nope

[11:07]  
they are in repository

[11:07]  
“instance mode"

[11:07]  
means you keep an instance of repository

[11:07]  
and instead using

[11:07]  
 ```myObject save.```

[11:07]  
you use

[11:08]  
 ```repository save: myObject```

Sabine Manaa [11:08 AM] 
aha!

Esteban Lorenzano [11:08 AM] 
but well… still on the missing part

Sabine Manaa [11:08 AM] 
so, I dont need  VORepository using: (VOMongoRepository database: 'xxx') do: [ self save ].

Esteban Lorenzano [11:09 AM] 
you will need a “thread local” strategy

Sabine Manaa [11:09 AM] 
(using:do: is the existing implementation of useRepository: aRepository during: aBlock )

Esteban Lorenzano [11:10 AM] 
ah yes

Sabine Manaa [11:10 AM] 
ok

[11:10]  
“thread local” ?

Esteban Lorenzano [11:10 AM] 
nevertheless I’m not sure it will work

Sabine Manaa [11:11 AM] 
ok, i am listening

Esteban Lorenzano [11:11 AM] 
I implemented that

[11:11]  
it should be there

[11:11]  
I will recover and commit

[11:11]  
thing is

[11:11]  
since you can have a thread switch

[11:11]  
when multiple requests

[11:12]  
if you just change the singleton, it will not work

[11:12]  
for that is used a "thread local” (storing the var on the current thread) (edited)

[11:13]  
but the easiest way is to use the stack

[11:13]  
which is store the value in a dynamic variable

[11:14]  
and access it when required :stuck_out_tongue:

[11:15]  
I will do it, don’t worry

Sabine Manaa [11:15 AM] 
:smiley:

[11:16]  
so, for now, can I just use `repository save: myObject` (we are not yet online, so no problem with data...)

[11:16]  
?

[11:17]  
Did I understand right, that I dont need `VORepository using: (VOMongoRepository database: 'xxx') do: [ self save ].` as discussed first?

[11:17]  
and I would NOT turn on singleton mode (edited)

Esteban Lorenzano [11:17 AM] 
yes

[11:17]  
you can keep the repository on your user’s session

[11:17]  
and take it each time

[11:18]  
and not use singleton mode

[11:18]  
but using a filter and keeping singleton mode is a lot more confortable

[11:18]  
I wonder if you know the notion of seaside filters?

Sabine Manaa [11:18 AM] 
using a filter? no i dont!

Esteban Lorenzano [11:19 AM] 
that’s why you didn't got the original idea :slightly_smiling_face:

[11:19]  
so you can create seaside filters

[11:19]  
any kind

[11:19]  
and declare your app with your filters

[11:20]  
(that’s how seaside works, by the way: it stacks filters and “rendering” is just one kind of a filter)

[11:20]  
the idea is that you add your “repository filter”

[11:21]  
and you use your app is if it would be just a singleton

Sabine Manaa [11:21 AM] 
and because it knows the session it automatically knows the repository when saving?

Esteban Lorenzano [11:21 AM] 
the app will behave as is

[11:21]  
if you do it as I explained

[11:21]  
during the execution of your request

[11:21]  
the “singleton” will be what you want

Sabine Manaa [11:23 AM] 
for my understanding: eg curently user clicks on „save“, then i have an ajax request. in this ajax request, i currently do `aPersonInstance save`

[11:23]  
where is the point where the seaside filter will grab this?

Esteban Lorenzano [11:26 AM] 
a lot before

[11:26]  
filter is before

Udo Schneider [11:27 AM] 
You can also extend `WASession` to store the repo on a per session base. Then this would become `self session repository save: aPersonInstance` in a `WAComponent`. Outside of it you might have to resort to `WACurrentRequestContext value repository save: aPersonInstance`. This of course implies that your `WASession` subclass implements `#repository`.

Esteban Lorenzano [11:28 AM] 
is the same

[11:28]  
you will always need to store repository in your session

Sabine Manaa [11:28 AM] 
the same as estebans 2nd idea without the filter thing

[11:29]  
yes this is clear to me and no problem. But the thing with the filter is better?

[11:30]  
store the repo on a per session base was my initial plan.

Esteban Lorenzano [11:30 AM] 
the filter will allow you to use “singleton mode” in a non singletonnish context

[11:30]  
personally I like

[11:31]  
to do

[11:31]  
 ```Person selectOne: [ … ]```

[11:31]  
etc.

[11:31]  
instead

[11:31]  
 ```repository selectOne: Person where: [ … ]```
(edited)

[11:31]  
but is a matter of taste :wink:

Sabine Manaa [11:33 AM] 
can you give me an entry point how to define a filter (where to call the above `using:do:`)

[11:33]  
how to initiate the filter

[11:33]  
and can it be used within an ajax request?

Udo Schneider [11:35 AM] 
Another idea is to define your own `DynamicVariable` subclass - e.g. `CurrentVoyageRepository` which `#default`s to: `[ WACurrentRequestContext value session repository ] on: WARequestContextNotFound do: [ :ex | nil ]`. This will also allow you to use a "current" Repository outside of Seaside. E.g. for background jobs: `CurrentVoyageRepository value: backgroundRepo during: [self doSomething: WACurrentVoyageRepository  value]`.

Esteban Lorenzano [11:36 AM] 
that’s what I was explaining, @udos  :slightly_smiling_face:


Udo Schneider [11:36 AM] 
sorry - too many messages. Information overflow :slightly_smiling_face:

[11:38]  
`DynamicVariable` is one of those things which "smells global". But it really solves some otherwise ugly designs ...

Sabine Manaa [11:38 AM] 
At the moment it seems to me that in my case it is sufficient to use the session-solution. I always have the person, trip etc in my session and if I also have the repository (which is always the same for one person/company), I can say `aRepository save: aPerson` . But I can not definifely say it because I did not yet use filters.

Esteban Lorenzano [11:39 AM] 
So you do a

[11:39]  
child of

[11:39]  
WARequestFilter

[11:39]  
and redefine

[11:39]  
 ```handleFiltered: aRequestContext
    VOCurrentRepository 
        value: self obtainSessionRepository
        during: [  self next handleFiltered:  aRequestContext]

obtainSessionRepository
    ^ self session 
    propertyAt: #repository 
    ifAbsentPut: [ self newRepositoryForUser ]

newRepositoryForUser
   ^  VOMongoRepository database: 'client-database'
```
(edited)

Sabine Manaa [11:41 AM] 
and in the ajax request, is this context also given?

Esteban Lorenzano [11:42 AM] 
now, for that to work, you will need something I need to commit :slightly_smiling_face:

[11:42]  
give me half an hour

Sabine Manaa [11:42 AM] 
:grinning:

Esteban Lorenzano [11:43 AM] 
it was already implemented, just lost in the limbo

Sabine Manaa [11:45 AM] 
indeed - I have an idea now and then I would not need to change my code (my senders of save eg). this is great and very comfortable. :slightly_smiling_face: (edited)

[11:47]  
could you make a version on github?

Esteban Lorenzano [12:33 PM] 
so well

[12:33]  
I committed to voyage

[12:34]  
check VOCurrentRepository

[12:34]  
and VODynamicContainer

[12:34]  
(I just do versions on github now :P)

Sabine Manaa [12:35 PM] 
great, i will wait for the versions on github. I the meantime I started to implement the other stuff.

[12:38]  
one question: Am I right that i will switch to instance mode with the filter solution?

Esteban Lorenzano [12:52 PM] 
it is committed on master

Sabine Manaa [12:53 PM] 
are you planning to make a version, too?

Sabine Manaa [1:11 PM] 
Esteban, thank you very much for your work. I  have to finish for today. I have all my test data creation prepared now in different repositories with authentication and a WARequestFilter subclass: #RKARequestFilter added to the application. Tomorrow I will try what it does with your todays changes in the ajax requests :slightly_smiling_face:

Pierce Ng [1:26 PM] 
if #obtainSessionRepository is doing self session propertyAt:ifAbsentPut: then this is similar to subclassing WASession?

Sabine Manaa [1:29 PM] 
>>obtainSessionRepository is a method in my WARequestFilter subclass: #RKARequestFilter

[1:30]  
and I assigned the request filter to my application in sth like:

[1:30]  
`    theApplication := WAAdmin register: RKALayoutView asApplicationAt: theApplicationName.
    ...
    theApplication
        addLibrary: RKAConfiguration library;
        preferenceAt: #sessionClass put: RKASession;
        preferenceAt: #rootClass put: RKATask;
        *addFilter: RKARequestFilter new.*` (edited)

[1:31]  
I also have a subclass of WASession but this is another topic imho

Pierce Ng [1:31 PM] 
so your RKASession can also figure out by looking at your super database which user should use which repository, yes?

Sabine Manaa [1:32 PM] 
yes

Esteban Lorenzano [1:33 PM] 
Pierce Ng
if #obtainSessionRepository is doing self session propertyAt:ifAbsentPut: then this is similar to subclassing WASession?
Posted in #databasesYesterday at 1:26 PM

[1:33]  
yes

[1:33]  
just you don’t need to subclass it :slightly_smiling_face:

Pierce Ng [1:35 PM] 
i have been doing WASession subclasses and  was just thinking i don't want to deal with one subclass per application. will try this. :slightly_smiling_face: thanks.

Torsten Bergmann [2:57 PM] 
I agree with Esteban:

Person selectOne: [ … ]

is easier to write and understand than:

repository selectOne: Person where: [ … ]

On the other side I'm not sure if the second expression style by sending messages to the repo is better especially in debugging situations (evaluating the code on a specific production or "fake" repository to check a production situation easier)

Udo Schneider [3:02 PM] 
I used both approaches - and yes the singleton-mode style is simpler. However simplifying code from instance-mode to singleton-mode is easy (once you know singleton mode will be sufficient). Going the other way is PITA - been there, done that. Everything I do nowadays uses the instance mode initially. But with Estebabans addition it seems you can eat the cake and keep it ... so using multiple repos while maintaining the simpler API of singleton-mode.

Torsten Bergmann [3:11 PM] 
I like that (if one starts with singleton mode) one could easily add multiple repos with data separation later (to separate customers/user groups of the application).

Sabine Manaa [3:13 PM] 
@astares: me too and this is my case. Waiting for tomorrow morning working on this again....


----- Today February 16th, 2017 -----
Sabine Manaa [9:03 AM] 
Hi @estebanlm
`handleFiltered:` of my WARequestFilter subclass is done *before* registering of the new session in
`WAApplication handleDefault:
self handle: aRequestContext registering: self newSession`.
So in `obtainSesionRepository`, the session is not yet initialized.  Did I miss something? (edited)

Esteban Lorenzano [9:12 AM] 
mmm

[9:12]  
that’s not good

[9:14]  
I would send a mail to seaside list, I’m afraid I do not remember exactly how to solve that

[9:19]  
maybe there is a way to declare the filter *after* the session has been pushed

Sabine Manaa [9:32 AM] 
ok, I will have a look first and if I dont find it, I will ask in the mailinglist!

Sabine Manaa [12:47 PM] 
question: If I follow the idea from @udos and simply overwrite the save method (all my objects to be saved have one `RKAObject` superclass) with

[12:47]  
RKAObject>>save
    | theRepository |
    theRepository := [ WACurrentRequestContext value session customerRepository ]
        on: WARequestContextNotFound
        do: [ :ex | nil ].
    theRepository save: self. (edited)

[12:48]  
and set the repository of the session when login

[12:49]  
I have the same result - each object is saved in its belonging customerRepository

[12:50]  
Is there a disadvantage against the solution with the Filter subclass?

Stephan Eggermont [1:28 PM] 
@sabine: it would be useful to have a summarizing post to the mailing list. Would you be able to find the time for that?

Sabine Manaa [1:29 PM] 
@stephan do you mean that the discussion would be useful for others? If yes, I would do it.

[1:29]  
tomorrow

[1:30]  
also because of the 10.000 messages limit

[1:30]  
of slack

Stephan Eggermont [1:41 PM] 
Yes, it looks useful

Sabine Manaa [1:41 PM] 
ok, i just started and will post it

Stephan Eggermont [1:59 PM] 
Great

2017-02-16 14:00 GMT+01:00 Sabine Manaa [via Smalltalk] <[hidden email]>:
Hi,

the last days we had a an interesting discussion on slack.
Stephan Eggermont asked me to post a summary here for discussion, Information and because of the 10.000 messages limitation of slack:

Situation:
Til now I used Mongo/Voyage in singleton mode [1]. That means in short that I had one single database/repository in mongo. I then decided that I want to have one database/repository per customer (one customer has n persons) and one "super-database/repository" for data like currencies valid for all customers and for lookup of the right database for each user/email address. This has a lot of advantages. Mongo allows to have n databases/repositories within one installation.

First, I thought, that I have to change my code from

"aPersonInstance save"
(this is the singleton mode way, always same repository)

to "aCustomerRepository save: aPersonInstance"
(instance mode - so that mongo knows which repository to use)

Then Esteban proposed to use a SeasideFilter. This would be a lot better.
He told me to create a subclass of WARequest Filter and implement:

"handleFiltered: aRequestContext
    VOCurrentRepository
        value: self obtainSessionRepository
        during: [  self next handleFiltered:  aRequestContext]

obtainSessionRepository
    ^ self session
    propertyAt: #repository
    ifAbsentPut: [ self newRepositoryForUser ]

newRepositoryForUser
   ^  VOMongoRepository database: 'client-database'"

He also implemented and released some more code for this (e.g. VOCurrentRepository) [2].
The idea/concept is, that seaside uses the right repository within the current request context  and one does not have to deal with the  repositories.

Udo Schneider suggested the DynamicVariable subclass and get it within the WACurrentRequestContext

I did not know/use about Seaside Filters and not about DynamicVariables, so it was very interesting.

Currently I think that perhaps for me overwriting the save method (I have a superclass of all my voyage root objects) would be sufficient for me. My customers repository is set at login and kept in the session.

RKAObject>>save
    | theRepository |
    theRepository := [ WACurrentRequestContext value session customerRepository ]
        on: WARequestContextNotFound
        do: [ :ex | nil ].
    theRepository save: self.

But I am not finished with this and opinions are welcome and perhaps this is useful for others.
 
There was a lot more discussion. I will put the whole discussion from slack in an answer of this post. So it is kept but not attached in answers to THIS post.

Sabine


 [1] http://files.pharo.org/books-pdfs/entreprise-pharo/2016-10-06-EnterprisePharo.pdf chapter on voyage
[2]https://github.com/pharo-nosql/voyage/commit/d1fa97e41470671452d3501421a7bd0ac60456e9



If you reply to this email, your message will be added to the discussion below:
http://forum.world.st/Mongo-Voyage-singleton-vs-instance-mode-and-seaside-Request-filters-tp4934578.html
To start a new topic under Seaside General, email [hidden email]
To unsubscribe from Seaside, click here.
NAML