Hi all,
Let's say I have a class Group and a class Member. aGroup has many members. and aMember can belong to many groups. What is the "proper" voyageDescription for those one to many relations? Should I do something extra? Everytime I add a member to the group, both the member and the group get their references updated. Now when I add a member to a group and save it, I also save the member. Is there another way to avoid this? Any recommended practice for this? Next level question: What if it gets composite? it is... a Group has other Groups as members :) Regards! Esteban A. Maringolo |
Hi Esteban,
What you have described is quite common and there are a variety of ways of designing this sort of object model. If you were dealing with a relational database, then you would have a Group table, a Member table, and a Membership table. The Membership table would have one row for each member/group pair and the member ID and group ID would be the primary columns (perhaps with other things like date joined, date expired, etc.). With an index on the member ID and group ID, one could quickly find all the groups for a member and all the members for a group, and each piece of data would exist in only one place ("normalized"). One could take a similar approach with objects, having three collections containing Groups, Members, and Memberships respectively. You could have an accessor in Group that returned members based on a select from the Memberships collection and an accessor in Member that returned groups based on a select from the Memberships collection. Alternatively, if you wanted to avoid the #'select:' call in one or both lookups, you can cache the respective subcollections in Group and/or Member and drop the Memberships collection. If you cache in only one place (e.g., each Group has a collection of Members), then you can add a method in Member that does a #'select:' of Groups where a Group has the Member. If you cache in both places then your getters/setters in each can update the other (with the understanding that you are duplicating data to get performance efficiency). Again, this problem is very common. Consider students in classes, persons with addresses, persons with hobbies, etc. Your choices are basically (1) three collections, no duplicates, select for both queries; (2) two collections, no duplicates, cache for one query, select for the other query; and (3) two collections, caches for each query. Which is preferable depends on your typical sizes and lookup patterns. If performance is not an issue, then start with whatever is simplest for you to code. I lean toward the three collections with the lookup penalty (GemStone's ability to index collections makes this approach very efficient), but if most of your lookups are from one side or the other and scanning the full collection is slow, then cache in one or both places. If you have a composite, the analysis is similar since Smalltalk does not require static typing of variables. You just need to take care that your accessors can give back the expected objects. James On Aug 29, 2013, at 6:12 PM, Esteban A. Maringolo <[hidden email]> wrote: > Hi all, > > Let's say I have a class Group and a class Member. > > aGroup has many members. > and aMember can belong to many groups. > > What is the "proper" voyageDescription for those one to many relations? > > Should I do something extra? > > Everytime I add a member to the group, both the member and the group > get their references updated. > Now when I add a member to a group and save it, I also save the > member. Is there another way to avoid this? > > Any recommended practice for this? > > Next level question: What if it gets composite? it is... a Group has > other Groups as members :) > > > Regards! > > Esteban A. Maringolo > > |
Hi James,
Thanks for your response. You are right, the Pattern is quite common, if not ubiquous in all systems. My particular question is related with Voyage+MongoDB, and also could apply to Magritte (given the fact Voyage "inherits" its descriptions model). I sucessfully implemented this pattern with ORMs using an intermediary table for the relation, or even reifying the relation as a first class object. I also used the #select: technique to avoid duplication of "responsibility" and caching at one side of the relation. But I always did it "in memory" with no mapping et all, just plain old smalltalk objects (POSO?) and using ORM solutions. What I need to know now is: 1) How to do this with voyage+MongoDB 2) I want to have a two way relation, it is both members and groups have a collection (members know their groups and viceversa). This requires additional maintenance, but with a higher performance, and doesn't require me to load all groups if I'm just materializing one member. Regards! Esteban A. Maringolo 2013/8/30 James Foster <[hidden email]>: > Hi Esteban, > > What you have described is quite common and there are a variety of ways of designing this sort of object model. If you were dealing with a relational database, then you would have a Group table, a Member table, and a Membership table. The Membership table would have one row for each member/group pair and the member ID and group ID would be the primary columns (perhaps with other things like date joined, date expired, etc.). With an index on the member ID and group ID, one could quickly find all the groups for a member and all the members for a group, and each piece of data would exist in only one place ("normalized"). One could take a similar approach with objects, having three collections containing Groups, Members, and Memberships respectively. You could have an accessor in Group that returned members based on a select from the Memberships collection and an accessor in Member that returned groups based on a select from the Memberships collection. > > Alternatively, if you wanted to avoid the #'select:' call in one or both lookups, you can cache the respective subcollections in Group and/or Member and drop the Memberships collection. If you cache in only one place (e.g., each Group has a collection of Members), then you can add a method in Member that does a #'select:' of Groups where a Group has the Member. If you cache in both places then your getters/setters in each can update the other (with the understanding that you are duplicating data to get performance efficiency). > > Again, this problem is very common. Consider students in classes, persons with addresses, persons with hobbies, etc. Your choices are basically (1) three collections, no duplicates, select for both queries; (2) two collections, no duplicates, cache for one query, select for the other query; and (3) two collections, caches for each query. Which is preferable depends on your typical sizes and lookup patterns. If performance is not an issue, then start with whatever is simplest for you to code. I lean toward the three collections with the lookup penalty (GemStone's ability to index collections makes this approach very efficient), but if most of your lookups are from one side or the other and scanning the full collection is slow, then cache in one or both places. > > If you have a composite, the analysis is similar since Smalltalk does not require static typing of variables. You just need to take care that your accessors can give back the expected objects. > > James > > On Aug 29, 2013, at 6:12 PM, Esteban A. Maringolo <[hidden email]> wrote: > >> Hi all, >> >> Let's say I have a class Group and a class Member. >> >> aGroup has many members. >> and aMember can belong to many groups. >> >> What is the "proper" voyageDescription for those one to many relations? >> >> Should I do something extra? >> >> Everytime I add a member to the group, both the member and the group >> get their references updated. >> Now when I add a member to a group and save it, I also save the >> member. Is there another way to avoid this? >> >> Any recommended practice for this? >> >> Next level question: What if it gets composite? it is... a Group has >> other Groups as members :) >> >> >> Regards! >> >> Esteban A. Maringolo >> >> > > |
Hi Esteban,
It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. James On Aug 30, 2013, at 11:13 AM, Esteban A. Maringolo <[hidden email]> wrote: > Hi James, > > Thanks for your response. You are right, the Pattern is quite common, > if not ubiquous in all systems. > > My particular question is related with Voyage+MongoDB, and also could > apply to Magritte (given the fact Voyage "inherits" its descriptions > model). > > I sucessfully implemented this pattern with ORMs using an intermediary > table for the relation, or even reifying the relation as a first class > object. > I also used the #select: technique to avoid duplication of > "responsibility" and caching at one side of the relation. > But I always did it "in memory" with no mapping et all, just plain old > smalltalk objects (POSO?) and using ORM solutions. > > What I need to know now is: > 1) How to do this with voyage+MongoDB > 2) I want to have a two way relation, it is both members and groups > have a collection (members know their groups and viceversa). This > requires additional maintenance, but with a higher performance, and > doesn't require me to load all groups if I'm just materializing one > member. > > > Regards! > > Esteban A. Maringolo > > > 2013/8/30 James Foster <[hidden email]>: >> Hi Esteban, >> >> What you have described is quite common and there are a variety of ways of designing this sort of object model. If you were dealing with a relational database, then you would have a Group table, a Member table, and a Membership table. The Membership table would have one row for each member/group pair and the member ID and group ID would be the primary columns (perhaps with other things like date joined, date expired, etc.). With an index on the member ID and group ID, one could quickly find all the groups for a member and all the members for a group, and each piece of data would exist in only one place ("normalized"). One could take a similar approach with objects, having three collections containing Groups, Members, and Memberships respectively. You could have an accessor in Group that returned members based on a select from the Memberships collection and an accessor in Member that returned groups based on a select from the Memberships collection. >> >> Alternatively, if you wanted to avoid the #'select:' call in one or both lookups, you can cache the respective subcollections in Group and/or Member and drop the Memberships collection. If you cache in only one place (e.g., each Group has a collection of Members), then you can add a method in Member that does a #'select:' of Groups where a Group has the Member. If you cache in both places then your getters/setters in each can update the other (with the understanding that you are duplicating data to get performance efficiency). >> >> Again, this problem is very common. Consider students in classes, persons with addresses, persons with hobbies, etc. Your choices are basically (1) three collections, no duplicates, select for both queries; (2) two collections, no duplicates, cache for one query, select for the other query; and (3) two collections, caches for each query. Which is preferable depends on your typical sizes and lookup patterns. If performance is not an issue, then start with whatever is simplest for you to code. I lean toward the three collections with the lookup penalty (GemStone's ability to index collections makes this approach very efficient), but if most of your lookups are from one side or the other and scanning the full collection is slow, then cache in one or both places. >> >> If you have a composite, the analysis is similar since Smalltalk does not require static typing of variables. You just need to take care that your accessors can give back the expected objects. >> >> James >> >> On Aug 29, 2013, at 6:12 PM, Esteban A. Maringolo <[hidden email]> wrote: >> >>> Hi all, >>> >>> Let's say I have a class Group and a class Member. >>> >>> aGroup has many members. >>> and aMember can belong to many groups. >>> >>> What is the "proper" voyageDescription for those one to many relations? >>> >>> Should I do something extra? >>> >>> Everytime I add a member to the group, both the member and the group >>> get their references updated. >>> Now when I add a member to a group and save it, I also save the >>> member. Is there another way to avoid this? >>> >>> Any recommended practice for this? >>> >>> Next level question: What if it gets composite? it is... a Group has >>> other Groups as members :) >>> >>> >>> Regards! >>> >>> Esteban A. Maringolo >>> >>> >> >> > > |
2013/8/30 James Foster <[hidden email]>:
> It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. Thanks anyway, it is a problem you'll never find in GemStone/S ;-) |
More or less, I have this working.
In the Group class I have a description named: descriptionMembers <mongoDescription> ^VOMongoToManyDescription new attributeName: 'members'; accessor: #members; kind: Member; beLazy; yourself And in the Member class I have descriptionGroups <mongoDescription> ^VOMongoToManyDescription new attributeName: 'groups'; accessor: #groups; kind: Group; beLazy; yourself But what if Member is an abstract class and the elements can be any of SingleMember or ComplexMember?, each one is stored in a separate collection in Mongo. If I do specify #kind: Member in my #descriptionMembers it won't work. Any ideas? Regards, Esteban A. Maringolo 2013/8/30 Esteban A. Maringolo <[hidden email]>: > 2013/8/30 James Foster <[hidden email]>: >> It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. > > Thanks anyway, it is a problem you'll never find in GemStone/S ;-) |
I think that the question could be rephrased as:
How to map a collection with elements of different classes? Am I asking for too much? Esteban A. Maringolo 2013/8/30 Esteban A. Maringolo <[hidden email]>: > More or less, I have this working. > > In the Group class I have a description named: > descriptionMembers > <mongoDescription> > ^VOMongoToManyDescription new > attributeName: 'members'; > accessor: #members; > kind: Member; > beLazy; > yourself > > > And in the Member class I have > descriptionGroups > <mongoDescription> > ^VOMongoToManyDescription new > attributeName: 'groups'; > accessor: #groups; > kind: Group; > beLazy; > yourself > > > > But what if Member is an abstract class and the elements can be any of > SingleMember or ComplexMember?, each one is stored in a separate > collection in Mongo. > > If I do specify #kind: Member in my #descriptionMembers it won't work. > > Any ideas? > > Regards, > > > Esteban A. Maringolo > > > 2013/8/30 Esteban A. Maringolo <[hidden email]>: >> 2013/8/30 James Foster <[hidden email]>: >>> It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. >> >> Thanks anyway, it is a problem you'll never find in GemStone/S ;-) |
Am 31.08.2013 um 02:18 schrieb Esteban A. Maringolo <[hidden email]>: > I think that the question could be rephrased as: > > How to map a collection with elements of different classes? > > Am I asking for too much? > No, but this isn't a trivial problem. For your specific case it is probably not implemented. I still didn't find the time to look into voyage but I think it has basically two modes: Either you use it plain (no magritte) and types are inferred from the object. Voyage then adds the information about the class in the json being serialized. If you use magritte then it can take the information from the description. So your case is a mix of the two. You can take basic information from the description but if it comes to inheritance you still need to add the class information in the json to be able to deserialize properly. The sad story about this is that relationships aren't perfect in magritte and nobody finds time to work out a proper solution. For your case and other problems there needs to be a fix and having separate descriptions for homogenous and heterogenous collections might be an approach that solves a few things. Norbert > > Esteban A. Maringolo > > > 2013/8/30 Esteban A. Maringolo <[hidden email]>: >> More or less, I have this working. >> >> In the Group class I have a description named: >> descriptionMembers >> <mongoDescription> >> ^VOMongoToManyDescription new >> attributeName: 'members'; >> accessor: #members; >> kind: Member; >> beLazy; >> yourself >> >> >> And in the Member class I have >> descriptionGroups >> <mongoDescription> >> ^VOMongoToManyDescription new >> attributeName: 'groups'; >> accessor: #groups; >> kind: Group; >> beLazy; >> yourself >> >> >> >> But what if Member is an abstract class and the elements can be any of >> SingleMember or ComplexMember?, each one is stored in a separate >> collection in Mongo. >> >> If I do specify #kind: Member in my #descriptionMembers it won't work. >> >> Any ideas? >> >> Regards, >> >> >> Esteban A. Maringolo >> >> >> 2013/8/30 Esteban A. Maringolo <[hidden email]>: >>> 2013/8/30 James Foster <[hidden email]>: >>>> It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. >>> >>> Thanks anyway, it is a problem you'll never find in GemStone/S ;-) > |
In reply to this post by Esteban A. Maringolo
Hi,
I just found some time to see this... there is a problem with saving/updating circular references that I "solved" by not solve it at all :( here is the thing: when you create a new group and add a new user, the #save message will find the new objects (in this case group and user) and save both. Now, when you are updating, I do not have any way (so far), to know which part of the graph is actually updated and needs to be saved (and we all will agree that persisting each time all structure will not be efficient). So I just do not do it, I'm sorry :( That mean that if the referenced object is not new, it is not stored... just the "first level" object is... I know is not a good solution, but what I do is to save each element of the graph separated: user := User new save. group := Group new add: user; save. so, taking each updated element as a unity will do the job. I know, I know... this is not optimal (and in fact is pretty bad), but well... is what I could do at the moment and never had the time to enhance it. Esteban On Aug 30, 2013, at 3:12 AM, Esteban A. Maringolo <[hidden email]> wrote: > Hi all, > > Let's say I have a class Group and a class Member. > > aGroup has many members. > and aMember can belong to many groups. > > What is the "proper" voyageDescription for those one to many relations? > > Should I do something extra? > > Everytime I add a member to the group, both the member and the group > get their references updated. > Now when I add a member to a group and save it, I also save the > member. Is there another way to avoid this? > > Any recommended practice for this? > > Next level question: What if it gets composite? it is... a Group has > other Groups as members :) > > > Regards! > > Esteban A. Maringolo > |
In reply to this post by Esteban A. Maringolo
Hi,
what happens is that if you declare a "kind", you forces all elements to be of that specific class. Solution is just not add that property: <mongoDescription> ^VOMongoToManyDescription new attributeName: 'members'; accessor: #members; "kind: Member; " "<-- NO!" beLazy; yourself. And same happens in the <mongoContainer> description in the top of your hierarchy... you must NOT declare a kind. Also, if you want all members to go in just one collection. #isVoyageRoot has to be implemented in the top of the hierarchy and not in the subclasses Member "<-- #isVoyageRoot = true here!" SingleMember ComplexMember what happens then is that voyage will create a collection and for each document it stores in it will also store the attribute '#instanceOf', which will be used to retrieve the correct object. Esteban what happens then is that On Aug 31, 2013, at 12:53 AM, Esteban A. Maringolo <[hidden email]> wrote: > More or less, I have this working. > > In the Group class I have a description named: > descriptionMembers > <mongoDescription> > ^VOMongoToManyDescription new > attributeName: 'members'; > accessor: #members; > kind: Member; > beLazy; > yourself > > > And in the Member class I have > descriptionGroups > <mongoDescription> > ^VOMongoToManyDescription new > attributeName: 'groups'; > accessor: #groups; > kind: Group; > beLazy; > yourself > > > > But what if Member is an abstract class and the elements can be any of > SingleMember or ComplexMember?, each one is stored in a separate > collection in Mongo. > > If I do specify #kind: Member in my #descriptionMembers it won't work. > > Any ideas? > > Regards, > > > Esteban A. Maringolo > > > 2013/8/30 Esteban A. Maringolo <[hidden email]>: >> 2013/8/30 James Foster <[hidden email]>: >>> It seems that I mistook the question for something easy enough for me to answer! Unfortunately, I don't have any knowledge of Voyage+MongoDB so can't really give the proper answer. >> >> Thanks anyway, it is a problem you'll never find in GemStone/S ;-) > |
In reply to this post by EstebanLM
Esteban, That's totally fine for me. I've been doing explicit saves in ORMs since more than a decade. I can live with that. My question was to have more than one class of element in the same collection mapping.
Regards! Esteban A. Maringolo
2013/9/10 Esteban Lorenzano <[hidden email]> Hi, |
I just answered that :)
On Sep 10, 2013, at 5:00 PM, "Esteban A. Maringolo" <[hidden email]> wrote:
|
In reply to this post by EstebanLM
Esteban, I didn't know about the #isVoyageRoot difference based on where was the method defined. I mean... if I define Member class>>#isVoyageRoot
^true why sould SingleMember answer false? Do I have to redefine it to answer false in each of Member subclasses? Regards! Esteban A. Maringolo
2013/9/10 Esteban Lorenzano <[hidden email]> Hi, |
no, it should not be implemented, no need to make him answer false (in fact, I never tried, but I'm pretty sure that is you reimplement isVoyageRoot to answer false in the subclasses most probably you will get a class that is transient :( ).
reason is that voyage looks the root class of a hierarchy by noticing where is #isVoyageRoot = true implemented. (again, each time I look in this "design decisions" I feel a bit ashamed... but well, they looked good at the moment :) ) Esteban On Sep 10, 2013, at 5:06 PM, Esteban A. Maringolo <[hidden email]> wrote:
|
That was the main error I had. Assuming the #isVoyageRoot was inheritable. After looking at #voyageCollectionName I understood all the problems I had. I learnt a lot about its internal during the process. Design question: Why #persistentClass answer false if a Class has no voyage root superclass? Shouldn't it raise an error or answer nil instead? Esteban A. Maringolo
2013/9/10 Esteban Lorenzano <[hidden email]>
|
Free forum by Nabble | Edit this page |