Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Should data objects and database accessors exist in the same class? 2

Status
Not open for further replies.

noodle22

Programmer
May 8, 2008
23
Consider this example. Suppose that I have a Store object and a Product object. The properties in each class are

Store
-String Name
-String Location
-IList<Product> Products

Product
-Name
-ProductNumber

Now I am using some OR mapper to populate these objects. My current method of relating these objects is

-I have a namespace for the objects themselves
-And I have a namespace for database accessors that populate and make changes to the repositories of each of these objects.

So my database accessors might look something like
IStoreAccessor
-GetStore(int StoreID)
-Save(Store store)

IProductAccessor
-GetProduct(int ProductID)
-Save(Product product)
-Search(Store store, String productNameLike)

So, my question is should this search method actually be part of the IStore object (not IStoreAccessor)so that I can do something like myStore.Search("OO analysis & design book")? It seems like it could be harder for me to maintain database wise if I have a database access function existing as part of my entity, but, maybe it is how things are supposed to be done? I ask because I found an example project recently where that was the case (they included the search function in the IStore interface). If Search should go in IStore, then should Save go in there as well?


 
I don't like any database code in entities, nor "collection" functionality in them. What you call "accessors" are named collections in my programs. So I have a Stores collection that provide access to Store objects. How, that is an internal responsibility. Usually, my collections are lazy (meaning they cache objects and do not load them until asked), but the database strategy (lazy/greedy) is an internal responsibility as well.

I think a search is not a store's responsibility, unless Store implements IProductAccessor (and maybe delegates it to an internal IProductAccessor object). Whether this improves legibility is up to you: the calls to the search method can get shorter, but there are more methods on the Store object and you will have to pass connectivity details to each store.



+++ Despite being wrong in every important aspect, that is a very good analogy +++
Hex (in Darwin's Watch)
 
I'm using nhibernate as my OR mapper and I used MyGeneration to come up with my initial mappings. It created collections in my objects for the different relationships and I must say, I find them pretty useful. NHibernate handles all the lazy loading for me so I don't have to worry about it too much.

The idea about using an internal IProductAccessor object within an instance of IStore makes sense if we were to implement product Search functionality for a Store object. So, supposing we did this, we still have a database accessor within an entity. Is this a good practice?

In your programs, you wouldn't name a something like my IStoreAccessor an IStoreCollection would you? My db accessor only gets or saves a single instance of an IStore object and doesn't have a lot to do with collections. But the IProductAccessor gets and saves a single instance of a product as well as gets a collection so maybe that is a grey area. I thought this was an interesting point you brought up and I am still working on better organizing my projects so I created a new thread and I hope you can provide some insight

 
Actually, my storage handling is a organized a bit differently. I use dependency injection to make the data storage aware of the collections, instead of the other way around.

Off course, most of the collections must have a way of requesting data from storage, so some of these accessor classes are passed to the collections as well. Main thing is that an accessor can select a row by ID or store a given row. The collections are "susceptible" to data from any accessor (whether known to them or not). The accessors are always connected to their target collection.
My accessors are just "data lines" that can be connected to a collection like a telephone operator connects wires to a customer. Advantage of this abstraction is that any kind of storage can be used: QueryLines for SQL, IsamLines for ISAM-style selection, ArrayLines for unit testing, etc.

datalinesuml.gif


But I wildly digress.

I do not have a "best" method of organizing composite structures. Sometimes, a data line serves more than one collection, or a collection passes part of a row to another. I tend to make my data objects not too intelligent though. Sometimes it is better to just get the StoreID and use it in the Products.search method.
Sometimes, I build partial collections. Such a collection could be a ProductsForStore and implement its own search.
And sometimes I pass a reference of the Products collection to the Stores collection, which passes it on to its individual members. This basically gets the StoreID also for use in the search, but it does so internally. So no external code needs to be bothered with a StoreID.

Like I said, I do not have a "best" practise. Passing fellow collections is optimal for the outside code, but causes overly tight coupling between the collections. Exposing the IDs solves this, but sacrifices encapsulation.

I do not think exposing database IDs in object structures is always bad. Especially within the data objects, you always need some contact with the storage. So the closer an object is to the storage layer, the more acceptable it is to show its ID.

Another way of avoiding coupling between collections is to organize them all on one object, that is passed instead of all necessary fellow collections. I usually call this the Backend object, and it has getters for each collection. But that also exposes the collections to places where they have little use. Oh well. The world just isn't perfect.

Hope this is of any help...

+++ Despite being wrong in every important aspect, that is a very good analogy +++
Hex (in Darwin's Watch)
 
if you're using NHibernate, then you should not need any accessors (DAO, repositories) within the domain objects. NHibernate manages them for you. what you would need are domain aggragates (see Evans, Domain Driven Design).

Aggregates are the access point between your request and the domain. Store would be an aggregate for products. if you need to manage products within a store you would do so via the store.

if you wanted to search the store and limit the number of products I would do this with crieria. for assistance specific to nhibernate check out
Jason Meckley
Programmer
Specialty Bakers, Inc.
 
sorry that I am not fully getting this but I think I am having trouble asking my question. I can't phrase it right.

In the case above, would you have your function that handles the criteria within a method in store? ie Store.Search(searchstring)

and then Search would contain the criteria that gets nhibernate to go to the database and return the query results (so my store object is linked to the database access method, nhibernate, since a reference to nibernate now has to exist within the store's search method)? Or would you have Somehelperclass.Search(store, searchstring) in which case, store is completely independent of my database access method and I could switch to linq, or some other data access method and not have to change store at all.
 
this is how I would approach the problem
Code:
public IEnumerable<ProductDto> SearchStoreForProducts(...)
{
   using(ISession session = SessionFactory.OpenSession())
   {
      IStore store = session
         .CreateCriteria<IStore>()
            .Add(Expression.EqId(storeId))
         .AddCrieria("products", JoinType.Select)
            .Add(Expression.Eq("a property of Product", theValue))
         .UniqueResult<IStore>();
      return store.MapProductsUsing(productToDtoMapper);
   }
}
there are not guanetees on the exact API calls to Crieria. I'm still learning.
this should exeucte 2 queries.
Code:
select * from store where id=@id
select * from product where storeid=@storeid and acolumn = @avalue
store encapsulates products. I use a mapper to map the product to a dto and send the dto to view. within create criteria I would fetch the store and products based on the user input.

store and product are domain objects and have no knowledge of the data access components. my session is responsible for managing my domain object's interaction with the database.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
store and product are domain objects and have no knowledge of the data access components.

ok, I think that answers my question. And then your public IEnumerable<ProductDto> SearchStoreForProducts(...) would be contained in some other class somewhere (but not within store). Thanks for the detailed response (again:)

Also, DonQuichote, I totally missed your post when I last checked this thread. That is fantastic that you included a picture. Does this structure that you are talking about have an official name that I can use to read more about it? Or is it just a way that you have developed over time?
 
Yes, SearchStoreForProducts would be a member of the StoreService object. I would use this service to communicate between the presenter and the domain.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
awesome. ok, that makes sense to me. I think I have to work on my naming conventions a bit next time I refactor :)
 
Does this structure that you are talking about have an official name that I can use to read more about it? Or is it just a way that you have developed over time?

It is just a structure that I have developed over time. However, the technique of putting control on the other side of a relationship is called "dependency injection". It basically reverses the "parent" and the "child" of a dependency relationship. This usually helps reducing the tightness of coupling.

For instance, I started with a database class which was very polular (in being passed around through the code, that is). This meant that almost my entire codebase was dependent on that very class. I could have extracted an interface and thereby create the possibility of passing different database classes, but that has a few drawbacks:
I was already late with the interface extraction, so the existing interface of the database class was already non-generic. Furthermore, every class I substitued for it had to act like a database.

This is the point that I heard about dependency injection and worked towards a solution that incorporated "storage" instead of entire database functionality. One of my problems was in unit testing code that was database dependent. Some code needs data from a storage or tests that they can interact with a storage. But most of my storage is ISAM style: "this table, that index, number 5 please". I rarely build complex search queries and if I do, that is not the standard way of requesting persisted data. The more object-oriented I program, the simpler my SQL queries have become.

Later additions to this model were due to the client-server model of SQL databases that crept back in. For instance, it can be very ill-performing to issue a lot of single-row selecting queries if you do not need the results to be separate. This is why I added Preselect methods. A Preselect does nothing more than storing the requested ID internally, to be used along with the first Select call that is made afterwards. The Select method then causes a single query to be built with a "RequestedID IN ( ... )" clause instead of multiple "RequestedID=..." queries.

And that is also a beautiful thing of the data lines: nested data can be Preselected with other collections through their data lines. So if a Store would always need to know its special offers, it could preselect the corresponding ProductIDs with the appropriate data line and the Products collection would receive them in one batch if you requested either one of them. This is why the IsKeyKnown() method is defined, so a dataline can verify that it will not request things twice. Truth is that I do not use this feature very often and I may remove it from the model entirely.

If people are wondering what went wrong with the grey interfaces, here's the story:
First, my model started with the DataLineBasis interface, which were extended by both DataLineRead and DataLineWrite. Any class could then choose to implement DataLineRead, DataLineWrite, or both. If a class implements both, it automatically implements DataLineBasis as well, through both interfaces. This is what PHP refuses to support. Which is odd, because there is nothing wrong with it: There is no conflict in implementation possible in interfaces. And interfaces do support multiple inheritance in PHP.

+++ Despite being wrong in every important aspect, that is a very good analogy +++
Hex (in Darwin's Watch)
 
FWIW, the CSLA framework does this -- Lhotka has his database access code in the same class with his business entity (or DTO, don't recall which terminology he uses).

From a purists standpoint, I don't really care for it. But from a pragmatic standpoint of just getting stuff done, it works well enough.

Chip H.


____________________________________________________________________
www.chipholland.com
 
Another FWIW,
I have built using both models (persistance aware objects and Persistance aware Services), once the implementation is wired they are nearly the same to work with.

arguing about

MyPerson.Save or MyPerson.Search(Params)

VS

PersonManager.Save(MyPerson) or PersonManager.Search(Params)

never had enough of an argument to sway me either direction.

However, I tend to be a purist in how I architect my apps and always try to make the Business objects reflect actual real world Objects. There fore I would argue that an Object "Car" would not know how to persist itself any more than the real world car would. but some sort of Car Service would know how to persist cars.

To recap, For me "Real World" is a toss up. From an aesthetic point of view I prefer services that Know about persistance.

Tal
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top