UnitOfWork

Posts   
 
    
kulki
User
Posts: 26
Joined: 20-Dec-2004
# Posted on: 20-Dec-2004 16:33:16   

I noticed in the documentation that inorder to use UnitOfWork objects needs to be explicitly added to it. I don't understand why it has to be done this way. For example I implemented an architecture based on Martin Fowlers PoEAA and basically what I do is that when objects are created they are automatically added to the Identity Map and also changes are registered with UnitOfWOrk. Therefore I dont have to add objects explicitly to the UnitOfWork. Basically my code looks something like this.

UnitOfWork.NewCurrent(); // do your stuff like creating, updating, deleting

UnitOfWork.GetCurrent().Commit();

There is no need to add the objects to the UnitOfWork itself. I am just curious why its not implemented in this fashion too. Also in the documentation there is no mention of Identity Map. Is this pattern also implemented? thanks

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39752
Joined: 17-Aug-2003
# Posted on: 20-Dec-2004 17:32:44   

kulki wrote:

I noticed in the documentation that inorder to use UnitOfWork objects needs to be explicitly added to it. I don't understand why it has to be done this way. For example I implemented an architecture based on Martin Fowlers PoEAA and basically what I do is that when objects are created they are automatically added to the Identity Map and also changes are registered with UnitOfWOrk. Therefore I dont have to add objects explicitly to the UnitOfWork. Basically my code looks something like this.

UnitOfWork.NewCurrent(); // do your stuff like creating, updating, deleting

UnitOfWork.GetCurrent().Commit();

There is no need to add the objects to the UnitOfWork itself. I am just curious why its not implemented in this fashion too.

Because you can pass on a UnitOfWork object to multiple tiers without having a context present. For example you can create entities and order the unitofwork to save them in various ASP.NET pages, and then send the unit of work to a remoted service which actually persists the entities. This isn't possible in the Fowler model, which is always disconnected.

Also in the documentation there is no mention of Identity Map. Is this pattern also implemented? thanks

No, because creating really unique objects using an identity map requires a cross-appdomain object awareness or a central service which regulates this. In a distributed .NET application with multiple tiers this isn't possible.

Frans Bouma | Lead developer LLBLGen Pro
kulki
User
Posts: 26
Joined: 20-Dec-2004
# Posted on: 20-Dec-2004 22:19:55   

Well first of all Otis I am impressed by your answers. You certainly seem to know your stuff.

Otis wrote:

Because you can pass on a UnitOfWork object to multiple tiers without having a context present. For example you can create entities and order the unitofwork to save them in various ASP.NET pages, and then send the unit of work to a remoted service which actually persists the entities. This isn't possible in the Fowler model, which is always disconnected.

Well this is the classic problem I suppose where as an vendor you try to make the most general solution and then end up making things more complex. Now In my app I don't need any kind of remoted service. Just a simple 3 - tier architecture. I am assuming that the UnitOfWork.Commit() would most likely go in the GUI layer often times the end of button click. Now its a really pain for the GUI developer to keep adding the objects to UnitOfWork. And not just that the GUI needs to specify the Isolation level. I have a serious problem with GUI having to specify the Isolation level of the transaction. I think it outght to be specefied in the BL.

Otis wrote:

No, because creating really unique objects using an identity map requires a cross-appdomain object awareness or a central service which regulates this. In a distributed .NET application with multiple tiers this isn't possible.

Well if there is no Identity Map how do you solve the problem of multiple entities which represent the same row in the database?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39752
Joined: 17-Aug-2003
# Posted on: 21-Dec-2004 10:35:00   

kulki wrote:

Well first of all Otis I am impressed by your answers. You certainly seem to know your stuff.

Writing an O/R mapper requires you to make a lot of choices. simple_smile Making choices requires a lot of knowledge about the topic to avoid ending up with a mess.

Otis wrote:

Because you can pass on a UnitOfWork object to multiple tiers without having a context present. For example you can create entities and order the unitofwork to save them in various ASP.NET pages, and then send the unit of work to a remoted service which actually persists the entities. This isn't possible in the Fowler model, which is always disconnected.

Well this is the classic problem I suppose where as an vendor you try to make the most general solution and then end up making things more complex. Now In my app I don't need any kind of remoted service.

That doesn't matter much IMHO. It's the philosophy behind the architecture that matters. If the philosophy is 'disconnected', it's disconnected, even if you're using it in a 2-tier app. It's not more complex, it's more abstract and more generic, and altogether works in more situations than the fowler approach which is pretty limited. If you don't believe me, imagine a team of 5 people writing an ASP.NET application and 2 people in the team are the UI guys. How are you going to make them call BL methods for every data interaction instead of simply calling Commit() or other persistence method? You can't.

Just a simple 3 - tier architecture. I am assuming that the UnitOfWork.Commit() would most likely go in the GUI layer often times the end of button click. Now its a really pain for the GUI developer to keep adding the objects to UnitOfWork. And not just that the GUI needs to specify the Isolation level. I have a serious problem with GUI having to specify the Isolation level of the transaction. I think it outght to be specefied in the BL.

You're mixing things. On one hand you want a UnitOfWork which should do everything and on the other hand you don't want to specify a transaction isolation level. That doesn't add up. The GUI developer has 1 goal: creating a GUI. for the GUI he needs data to fill teh gui and functionality to make the gui's flow work, for example functionality which processes data from the GUI and provides him with new data. The GUI developer shouldn't worry about unit of work, he should just call BL code. The GUI developer can organize what HE thinks should be done with a unitofwork if the gui has a lot of screens to go through before teh BL code is again called. If not, why use a unitof work ?

Otis wrote:

No, because creating really unique objects using an identity map requires a cross-appdomain object awareness or a central service which regulates this. In a distributed .NET application with multiple tiers this isn't possible.

Well if there is no Identity Map how do you solve the problem of multiple entities which represent the same row in the database?

The only solution to that is to define application transactions, and inside that scope define unique objects per entity. This is impossible to do in a lot of applications. Only in desktop winform apps this is doable when they work on a local database.

For example what to do when 2 desktop apps target the same database on the network? Both read entity E. Both then have an instance of E in memory. However, they don't see eachother's changes as the instance CAN'T be the same, as .NET doesn't have cross-appdomain object awareness.

So it comes down to entity state, or better: application state and user state. There is a discussion about this in the documentation (in the Concepts section).

Frans Bouma | Lead developer LLBLGen Pro
kulki
User
Posts: 26
Joined: 20-Dec-2004
# Posted on: 21-Dec-2004 19:11:57   

Otis wrote:

That doesn't matter much IMHO. It's the philosophy behind the architecture that matters. If the philosophy is 'disconnected', it's disconnected, even if you're using it in a 2-tier app. It's not more complex, it's more abstract and more generic, and altogether works in more situations than the fowler approach which is pretty limited. If you don't believe me, imagine a team of 5 people writing an ASP.NET application and 2 people in the team are the UI guys. How are you going to make them call BL methods for every data interaction instead of simply calling Commit() or other persistence method? You can't.

I don't think I quite follow what you are trying to say here. Let me try to come up with an example to illustrate my point. Say that there is a business object User who has a property Name called FirstName. Now there is a Business Rule that whenever FirstName is changed a record gets added in the Log table which records the original value and the new value.

Therefore I might have code like this in the User Object

public string FirstName { get {....} set { Log log = new Log("FirstName", FirstName, value); this._firstName = value; } }

Basically I am creating a Log object which is marked for insert. I would expect the log object to be saved whenever the User is saved. Furthermore GUI programmer must not know anything about the Log object. Since I am using the UnitOfWork pattern I would expect all my changes to be persisted at the same time. In my existing architecture what I have done is the following:

UnitOfWork.NewCurrent(); User user = new User("Tom"); user.FirstName = "Thomas"; UnitOfWork.Commit();

In your architecture I know that there is a method called save. I don't quite see how that gets called? Does UnitOfWork.Commit() somehow invoke the save() method? and the save() invoked the Validator? What happens in case of errrors during Commit(). I am hoping that the object as well the database gets rolledback.

Otis wrote:

You're mixing things. On one hand you want a UnitOfWork which should do everything and on the other hand you don't want to specify a transaction isolation level. That doesn't add up. The GUI developer has 1 goal: creating a GUI. for the GUI he needs data to fill teh gui and functionality to make the gui's flow work, for example functionality which processes data from the GUI and provides him with new data. The GUI developer shouldn't worry about unit of work, he should just call BL code. The GUI developer can organize what HE thinks should be done with a unitofwork if the gui has a lot of screens to go through before teh BL code is again called. If not, why use a unitof work ?

I wanted to make things simple for the GUI programmer and just make a rule that he must always use the UnitOfWork for all cases. If you don't use UnitOfWork how do you ensure that everything is the context of a single database transaction? Are u suggesting that I do something like this?

entity1.Save(); entity2.Save(); In my case I just assumed that UnitOfWork would have an Isolation level of Read Commited.

Otis wrote:

No, because creating really unique objects using an identity map requires a cross-appdomain object awareness or a central service which regulates this. In a distributed .NET application with multiple tiers this isn't possible.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39752
Joined: 17-Aug-2003
# Posted on: 22-Dec-2004 10:32:46   

kulki wrote:

Otis wrote:

That doesn't matter much IMHO. It's the philosophy behind the architecture that matters. If the philosophy is 'disconnected', it's disconnected, even if you're using it in a 2-tier app. It's not more complex, it's more abstract and more generic, and altogether works in more situations than the fowler approach which is pretty limited. If you don't believe me, imagine a team of 5 people writing an ASP.NET application and 2 people in the team are the UI guys. How are you going to make them call BL methods for every data interaction instead of simply calling Commit() or other persistence method? You can't.

I don't think I quite follow what you are trying to say here. Let me try to come up with an example to illustrate my point. Say that there is a business object User who has a property Name called FirstName. Now there is a Business Rule that whenever FirstName is changed a record gets added in the Log table which records the original value and the new value.

Therefore I might have code like this in the User Object

public string FirstName { get {....} set { Log log = new Log("FirstName", FirstName, value); this._firstName = value; } }

Basically I am creating a Log object which is marked for insert. I would expect the log object to be saved whenever the User is saved. Furthermore GUI programmer must not know anything about the Log object. Since I am using the UnitOfWork pattern I would expect all my changes to be persisted at the same time. In my existing architecture what I have done is the following:

UnitOfWork.NewCurrent(); User user = new User("Tom"); user.FirstName = "Thomas"; UnitOfWork.Commit();

In your architecture I know that there is a method called save. I don't quite see how that gets called? Does UnitOfWork.Commit() somehow invoke the save() method? and the save() invoked the Validator? What happens in case of errrors during Commit(). I am hoping that the object as well the database gets rolledback.

Commit() on a unitofwork starts a transaction (or uses the one you pass in) and all actions of the unitofwork are done inside that transaction, an error rolls it back. The objects in a transaction are rolled back internally as well. You can also save field values along the way and roll back these values if you want.

As with your log example, the system has to know to which table to write teh log to. So a log entry is an entity as well and user has to have a relation with log. If you don't want that, (the relation) you can better use adapter, in which you can override the SaveEntity() method of DataAccessAdapter for example and in there check if entity.Fields[index].DbValue is different from entity.Fields[index].CurrentValue, if so, the value has been changed (DbValue is the value read from the db) and you can construct a log entry and save it directly from there using the DataAccessAdapter instance the method is in (thus this.SaveEntity(logentry); ). Which will run in the same transaction as the user entity save.

Otis wrote:

You're mixing things. On one hand you want a UnitOfWork which should do everything and on the other hand you don't want to specify a transaction isolation level. That doesn't add up. The GUI developer has 1 goal: creating a GUI. for the GUI he needs data to fill teh gui and functionality to make the gui's flow work, for example functionality which processes data from the GUI and provides him with new data. The GUI developer shouldn't worry about unit of work, he should just call BL code. The GUI developer can organize what HE thinks should be done with a unitofwork if the gui has a lot of screens to go through before teh BL code is again called. If not, why use a unitof work ?

I wanted to make things simple for the GUI programmer and just make a rule that he must always use the UnitOfWork for all cases. If you don't use UnitOfWork how do you ensure that everything is the context of a single database transaction? Are u suggesting that I do something like this?

entity1.Save(); entity2.Save(); In my case I just assumed that UnitOfWork would have an Isolation level of Read Commited.

Well, my POV is that in a team developers do what they have to do in a professional way. We're dealing with professionals here, not with students who just want to ruine the project. So if a GUI developer has to create a screen and the data in the screen has to be persisted, the screen doesn't work if the data isn't persisted. So the developer uses the functionality offered to the GUI developer to do so. Because the GUI developer is not allowed to make shortcuts, it is wise to use adapter, so the gui developer has to consult a different library (tier) to get data and to write data.

The gui developer can then create a unit of work and add entities which have to be saved to it and then pass on that unit of work to the BL tier (or store it in the session for the next screen, whatever is necessary). Either way, the developer writing the code knows what should be done, he just has to use the proper functionality to do it, and if you offer that in the form of a BL which uses internally a DataAccessAdapter object, the GUI developer can work with the unitofwork for adapter and at the same time can't persist any data nor call commit or rollback, that's up to the BL tier.

Calling Commit() is like calling Save(). If you don't want your gui developers to call Save() you can't use Commit() on the same level either. HOW the data is saved, i.e. by which calls, it's not important.

I especially avoided the dreaded 'context' approach every other O/R mapper uses, because it is very limiting in an application which has different layers and people developing a given layer are not allowed to use the 'context' object.

Frans Bouma | Lead developer LLBLGen Pro
kulki
User
Posts: 26
Joined: 20-Dec-2004
# Posted on: 23-Dec-2004 03:13:33   

Otis wrote:

Well, my POV is that in a team developers do what they have to do in a professional way. We're dealing with professionals here, not with students who just want to ruine the project. So if a GUI developer has to create a screen and the data in the screen has to be persisted, the screen doesn't work if the data isn't persisted. So the developer uses the functionality offered to the GUI developer to do so. Because the GUI developer is not allowed to make shortcuts, it is wise to use adapter, so the gui developer has to consult a different library (tier) to get data and to write data.

The gui developer can then create a unit of work and add entities which have to be saved to it and then pass on that unit of work to the BL tier (or store it in the session for the next screen, whatever is necessary). Either way, the developer writing the code knows what should be done, he just has to use the proper functionality to do it, and if you offer that in the form of a BL which uses internally a DataAccessAdapter object, the GUI developer can work with the unitofwork for adapter and at the same time can't persist any data nor call commit or rollback, that's up to the BL tier.

Calling Commit() is like calling Save(). If you don't want your gui developers to call Save() you can't use Commit() on the same level either. HOW the data is saved, i.e. by which calls, it's not important.

I especially avoided the dreaded 'context' approach every other O/R mapper uses, because it is very limiting in an application which has different layers and people developing a given layer are not allowed to use the 'context' object.

Well I guess it comes down to Trust boundaries. Typically I would like to have that the BL does not trust the GUI. So no matter how anyone codes the GUI the there should be no violation of the business rules.