The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Collection Design...

Question on building collection design.  Suppose a Widget can contain zero to may Foos.  We have classes:

Widget
Foo
FooCollection

..and Widget has a property Foos that returns a FooCollection with all the foos in that widget.

I'm stuck between implementing the collection like:

Widget.Foos.Add(new Foo("MyFoo"));

..or..

FooColletion foos = Widget.Foos;
foos.Add(new Foo("MyFoo"));
foos.SaveFoos();

In the former, the foo is added to the database as soon as the .Add is called.  In addition, I have to implement .Remove(), .AddAt(), and other methods to act on the database as soon as their called.

In the latter, you can .Add() or .Remove() all you want but until you call .SaveFoos() nothing is committed to the database.  In this implementation, I have to keep some kind of delta to measure what's been added and removed (or modified).

I know which way is easier for me to code it, what I'd like to know is, as developers, which way would you prefer the API behave?  If I'm not clear, the above examples are ways that a developer can use the API on my product to programatically manipulate foos for a widget.

Thanks for any insight.
smallbiz
Friday, December 10, 2004
 
 
"zero to may Foos"

should be

"zero to many Foos"
smallbiz
Friday, December 10, 2004
 
 
Can a Foo logically exist outside a Widget?  If not, then the FooCollection class should probably be contained within the Widget class so that you can't declare a FooCollection independent of a Widget.  This is why I find the second example syntax confusing; you're yanking the Foos out of the Widget and then saving them, when it seems like the Widget itself should be doing that, with either

Widget.Save  [implicitly saving the foos, since they're contained]

or

Widget.Foos.Save  [but only if there's a good reason to save the foos separately from the other Widget properties]
Kyralessa Send private email
Friday, December 10, 2004
 
 
1.  Of course a Foo has an independent existence, or he wouldn't have written his example that way.

2.  Where did this 'Database' come from?  I was reading away, when FOOM -- in came a database.

When you add a database, all these issues of database interfacing come in to the equation.  Setting up the SQL, executing the SQL, 'commiting' the SQL.  When do you do roll-back, if ever, etc.

In addition, adding elements now is not an 'atomic' operation (if it ever was) because the database connection and update must be taken into account.

A generic "Collection" is a way of keeping a group of Somethings.  Whether these Somethings have an independent existence in a database does not HAVE to be part of the Collection.

If you wrap the 'database' interface inside the 'Foo' creator, then you have to deal with database failures there as well.

If you have a separate Foo creator, which does not automatically store the Foo, then you have an opportunity for separate Foo storage methods, with more focussed error handling -- and more flexibility.

It's up to you.  If you percieve you need the extra flexibility, go with the separate Foo approach.
Not_muppet
Friday, December 10, 2004
 
 
"Can a Foo logically exist outside a Widget?"

Yes.  You can do:

Foo foo = new Foo("MyFoo");
Widget.Foos.Add(foo);

"Where did this 'Database' come from?"

Sorry.  I thought it was apparent that if I allow a developer to add and remove stuff that it should be persistant.  The database issues such as connections and executing sql that you mentioned are not the problem here as they're abstracted away from these classes.


"In addition, adding elements now is not an 'atomic' operation (if it ever was) because the database connection and update must be taken into account."

In the first example I gave they are still atomic as one foo at a time is being operated on.  In the second instance a transaction is needed to do deletes, updates, and inserts from the collection in one batch.

"If you have a separate Foo creator, which does not automatically store the Foo, then you have an opportunity for separate Foo storage methods, with more focussed error handling -- and more flexibility."

Yes, the opportunity is there, but I'm deliberately not taking it as I'd like the only way to save foos to be adding them to the Widget.Foos collection.  My reasoning for this is that I'm abstracting all programmatic ID's away from the API users to avoid them doing:

Foo foo = new Foo("MyFoo");
foo.WidgetId = widget.WidgetId;
foo.Save();

Too me it's much cleaner to just go:

widget.Foos.Add(new Foo("MyFoo"));

...and in the .Add of the collection I'm setting the WidgetId of the Foo so that it's foreign key is correct for the database save.

I believe that I'm going to go the direction of the first example where every .Add, .Remove, or whatever modifies the database so that I never have a situation of "data latency" where a collection is out of sync with the data store and needs to be saved.

Thanks for your ideas to keep me thinking.
smallbiz
Friday, December 10, 2004
 
 
"The database issues such as connections and executing sql that you mentioned are not the problem here as they're abstracted away from these classes."

OK, I understand better what you are trying to do, and how you are trying to do it.

"abstracted away from a class" does not mean it doesn't have to be done.  Any Database connection is a Leaky Abstraction.  You'd LIKE it to be 'perfect' -- you do an Insert, and poof the data is in the database for a later query, in zero time.

You'd LIKE it to be atomic -- by which I mean you say 'Insert', and poof the data is in there and you can proceed immediately to the next thing you want to do.  An Atomic action meaning nothing else can happen during the action.  Race conditions and so forth.

SOMEWHERE you are going to have to open a connection to a database, and SOMEWHERE you are going to do an 'Insert' or an 'Update' SQL query to get persistence of your data.  It's your code, you do it where it makes sense.

But you MUST be aware that you are NOT dealing with RAM, you are dealing with a Database.  Several things can happen in the Database you must be prepared to deal with -- and give your users some way to deal with.

I agree that having the database update in the .Add method is convenient, and allows your users a minimal way of creating a Foo.  As long as you know that the database access can throw an exception (or some other error mechanism).  Also, it would be nice if you knew that doing a whole bunch of individual Inserts may not be the fastest way of dealing with the database -- and it sounds like you are removing any other way from your users.

None of these things are show stoppers.  What you have proposed is a nice implementation.  That magical 'database' and 'persistence' just popping up in the middle told me you may be making assumptions which are not valid, which may bite you in the butt.
Not_muppet
Friday, December 10, 2004
 
 
I'm well aware of the issues in doing database programming and by "abstracting away from the class" I guess I meant to say "I know that some dirty stuff needs to be done and how to handle it but now I'm more concerned with how the end user would prefer to use the API"  :-)

I'm just trying to get things as clean as possible for my users as, being a developer, I've worked with waaaay too many hackneyed API's where I've spent hours banging my head because of unclear interfaces/implementation and/or poor documentation.
smallbiz
Friday, December 10, 2004
 
 
"Also, it would be nice if you knew that doing a whole bunch of individual Inserts may not be the fastest way of dealing with the database..."

I plan on making this clear in the documentation.  An educated programmer would probably be able to infer this for themselves just by looking at the API and how it works but, in my mind, the more complete the documentation, the less support calls I have to take.
smallbiz
Friday, December 10, 2004
 
 
I only really have one concern with the difference between aproach 1 and 2.  It is the fundamental difference between them.

  In approach 2 I am able to have a memory resident version of the foo colection that I can play with and even modify for temp use internal to the api as I am using it.  This lets me deside if I really want the changes later without having to make my own representation of foo to hold my working values for foo.

  In the first approach I never have to worry about being out of sync but if someone using your api has a need to temporaraly modify a foo in the widget collection and they do not want it to be permanent then the calling program will have to have its own collection of foo or some other storage method to do this.

  Both approaches are valid and usable.  I just spend 90% of my coding time working with data for EDI and other non glamerus reasons and there are times that it is very helpfull to have a non-persisant version of the data to manipulate then use and not worry about retaining the original values or make yet anouther copy.
Douglas Send private email
Friday, December 10, 2004
 
 
" there are times that it is very helpfull to have a non-persisant version of the data to manipulate then use and not worry about retaining the original values or make yet anouther copy."

That's a great point but in reality the foos are actually just name/value pair objects so I don't think my users will have too much need to create memory resident collections to play with before they commit.  I'm just trying to implement an [Object]:[Attributes] type thing where an attribute has a name and a value.  For example, if you take a Folder and want to apply some custom attributes to it just:

//add:
folder.Attributes.Add(new FolderAttribute("FolderIsHidden", "false"));

//delete:
folder.Attributes.Remove("FolderIsHidden");
or
folder.Attributes.Remove(folderAttribute);

//modify:
folder.Attributes["FolderIsHidden"].Value = "true";

//check
foreach(FolderAttribute folderAttribute in folder.Attributes)
{
  ...do whatever
}
smallbiz
Friday, December 10, 2004
 
 
In that case I would use approach one to keep the marginal ones from bitting themselves later.
Douglas Send private email
Friday, December 10, 2004
 
 
The second approach would allow you to batch a large number of changes into a single save().
Chitty Chitty Bang Bang
Monday, December 13, 2004
 
 
The second approach does indeed allow you to batch changes, so it's probably best to do that if the add operation is expensive, or is substantially cheaper if you do many at once. Of course you could always provide

Widget.addFoos(FooCollection foos);

The second approach requires you to deal with:

 FooCollection foos1=Widget.getFoos();
 FooCollection foos2=Widget.getFoos();
 foos1.addFoo(myfoo1);
 foos2.addFoo(myFoo2);
 Widget.saveFoos(foos1);
 Widget.saveFoos(foos2);
David Clayworth
Tuesday, December 14, 2004
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz