Unit Of Work Scoping
The key difference with desktop applications is that there is the potential for them to sit open and active for long periods of time – hours, days or weeks even. In these situations, it’s obvious that you do not want to necessarily be maintaining an open database connection the entire time.
It is important to try and identify or create the boundaries that your application will use to scope data access. In a sense, you’re creating the same type of fundamental work patterns as the request/response type nature of the web but based on your own application workflow. The benefits of doing this are that your application code is maintainable and your entity life cycle management is simple to work with.
While all applications should use a higher level design pattern for structure such as MVP, MVVM or MVC, it’s doubly important for desktop applications as they otherwise do not enforce any natural boundaries (such as managing the request/response life cycle). It takes discipline to take the freedom of the desktop and impose your own boundaries and stick to them.
The Unit of Work design pattern works best when tied to a certain section of actions to be undertaken. In an MVC structured application, for example, this means that while the rest of your application is doing various things, you are likely to only need to start and finish a unit of work in your controller – usually in very short lived manner. A short lived unit of work is the best way to use the pattern.
LightSpeed does not enforce that you use these patterns but you will find natural scoping boundaries more easily if you do work with these structural patterns.
One natural boundary in a desktop application is a pop up window or dialog. Take for example an application where you may wish to edit an employee’s details. The user double clicks an employee, a dialog appears and they can edit any of the employee details. They can then elect to save those changes or cancel the operation.
Here’s how you may wish to structure this type of interaction:
· Controller uses a unit of work to fetch the employee to edit
· Controller closes the unit of work
· Controller creates the view and exposes a model which the view can bind against (in this case, we could bind directly to our employee entities properties)
· At some later stage, after editing, the user clicks “Cancel” then the dialog closes.
· Or, at some later stage, after editing, the user clicks “Save” then the controller creates a new short lived unit of work, attaches the entity, and saves the changes.
· Everything is nicely and neatly done!
This represents an easy way to work with short lived units of work along with the boundaries enforced by your higher level design pattern to ensure you have a nice and clean way of working with entities. Of course, application specifics may be different – edit inline, certain users can edit only certain fields etc. – but the guidance above is a good starting point no matter what the user workflow.
If the user is going to work with a complex graph of objects all within one dialog (or other boundary), you’ll need to be wary of items that could be lazy loaded, or otherwise require an active unit of work to be associated with the entity. If you wish to make use of these capabilities you’ll need to look at using a longer running unit of work so the entity can maintain a connection to the database. See Long Running Units of Work below for guidance on this.
If you’re developing a multi-user system you will want to think about concurrency. For example, you may enable optimistic concurrency checking and trap that exception on save if another user has edited the same entity between you loading it and saving it. How you deal with such a concurrency violation would be application specific but it is a situation you will need to address. See Concurrent Editing in the chapter Implementing Storage Policies with LightSpeed for more information.
Often when building a desktop application there is the presumption that you can maintain a live data feed in the UI because you are removed from the request/response process. However, it’s wise to avoid this if you can, because rebinding entire grids or views means that you end up fighting the nuances of the user’s state (“they were editing field 3 in row 97 for the employee entity – we need to reset that cell to edit mode after each binding update”).
The best solution here is to make liberal use of “refresh” buttons in your application. You retrieve the data once and wait until the user has decided they wish to refresh the information before you actively go looking for changes.
Again, this is where the design patterns help out. Having a controller able to call a generic Refresh() method which rebinds the primary UI has the benefit of being easily callable from elsewhere in the controller. For example, if a user elects to edit an employee then when the controller has saved those changes it could also call the Refresh() method to ensure that as the dialog closes the primary UI is also updated to show the changes (and any other changes that other users may have made since the last refresh).
If after employing all of these techniques for making your desktop applications you find that there is still a need for a long running unit of work, then there are a number of points to bear in mind:
· If the database connection is lost the Unit Of Work will fail. To avoid this you may want to use a custom connection strategy to reconnect to the database if the connection fails for whatever reason. See Customising How LightSpeed Connects to the Database in the chapter Building Applications with LightSpeed for more information.
· Be wary of stale data. Entities held in the unit of work’s identity map or the global second‑level cache may become stale with time. Use methods such as UnitOfWork.Detach(Entity) to remove objects from the caches before re‑fetching to ensure you are obtaining fresh data.
· Be wary of concurrency concerns. In a multi-user system it’s important to make sure that data is not lost by saving stale data. LightSpeed always uses optimistic concurrency, so it won’t prevent two users editing the same record at the same time. Use features in LightSpeed such as Optimistic Concurrency Checking to prevent accidental data loss.