Use Case: gradual integration of Repository.Pattern with existing applications

Developer
Jul 11, 2014 at 2:20 PM
Edited Jul 16, 2014 at 9:55 PM

Scenario

The business has a large set of applications that would like to adopt the Repository.Pattern approach over time. The challenges are:
  • many shared libraries between applications that will need testing written to ensure correct adoption, but can be updated over time
  • libraries from external vendors that are hard to update/not possible to update
  • not wishing to disrupt the normal (high tempo) release cycle that includes other feature requests and bug fixes
    The main blocker to adoption is:
  • Service layers in external libraries take an injected DbContext can resolve Set<TEntity> on the context (leaving the correctness of that aside, this does exist) and call DbContext.Save.
  • However this will not save to the database as they do not know about IObjectContext when they create the entity. Therefore when the DataContext overrides for SaveChanges kick in, they will set the EF entity state to Unmodified
  • For a fake example see Northwind.Test.IntegrationTests.UnitOfWork_Tests_ChangeTracking.InsertWithoutChangeOverrideFails() in GenericUnitOfWorkABContrib 6d6578e5

Proposed Solution

Based on the following type of Unit of Work that encompasses multiple services calls. Internal service calls will be routed via the framework's Repository<T>, external services will go directly to the Set<T> on the DbContext

Use case
  • Each of these external service operations (due to the nature of the EF DbContext.SaveChanges method) can be viewed as "atomic" within the larger transaction controlled by UoW as they terminate with a SaveChanges call
  • Therefore provide the ability to "turn off" the Repository.Pattern's "conforming interface" for change tracking and let EF take over, either for entire UnitOfWork scopes, or for one-off EF-only operations performed by external services.
Initially this would let an adopter change the DbContext subtype to a DataContext subtype, but set the change tracking mode to let EF control change tracking as before. Services can then be upgraded individually by specifying the default Repository.Pattern change tracking as the default, and deal with Special Case exceptions using a temporary scope. Eventually the Repository.Pattern change tracking can be adopted as the norm, at which point the framework will have been completely integrated.

Request For Comment

Proof of concept code has been committed for comment (I am still testing it) in the branch GenericUnitOfWorkABContrib dev-cr-integration (inclusive of) from GenericUnitOfWorkABContrib 7157b4c4e30f to 06f4d37a419e.

Examples of setting the mode, or creating temporary scopes are in Northwind.Test.IntegrationTests.UnitOfWork_Tests_ChangeTracking
Developer
Jul 11, 2014 at 4:15 PM
Proof of concept code has been committed for comment (I am still testing it)
I have now integrated that branch into an application: external library 1 is fully Repository.Pattern aware, external library 2 is unaware, all integration tests are working so far. I can't see any major changes that are required to the above listed branch.
Coordinator
Jul 16, 2014 at 6:13 PM
Edited Jul 16, 2014 at 6:14 PM
I'm over-simplying here, but is there a way where we could setup a flag in the config, and if it's true, we just omit calling our Sync methods that sync IObjectState with EF's state and let EF's change tracking take over? We can catch up and do a quick meet if I'm missing something, which I probably am.
Developer
Jul 16, 2014 at 10:00 PM
Added example sequence diagram to original scenario.
Jul 16, 2014 at 10:22 PM
Just for curiosity, why are you integrating this technology into a system already established. (This answer is very difficult for people who are undecided, thats why im asking, to help my friends.)
Developer
Jul 16, 2014 at 11:18 PM
Various considerations such as:
  • this framework implements out of the box CRUD and querying with EF storage through its generic classes, enabling faster development of new features
    new Repository<Customer>(context, unitOfWork).Insert(customer); // ! it's that easy
  • designed for DI out of the box, testable and mockable
  • wrap and reuse existing IP assets inside new systems that use this framework, without complete rewrites of code
  • standardisation of service layer across new and existing code = lower maintenance costs and complexity
  • separation of service layer from ORM technology through Unit of Work and Repository patterns (Fowler, Patterns of Enterprise Application Architecture); essentially creating a "conforming interface" for business logic to work with
  • swap out legacy ORM technologies once they are encapsulated by these standard interfaces (swap DbCommand for EF, for example) without changing service layer code
Coordinator
Jul 16, 2014 at 11:31 PM
Reading over the added docs, I think I may have missed
Services can then be upgraded individually by specifying the default Repository.Pattern change tracking as the default, and deal with Special Case exceptions using a temporary scope. Eventually the Repository.Pattern change tracking can be adopted as the norm, at which point the framework will have been completely integrated.
"Services can then be upgraded individually"

Making a bit more sense now that I consider this clause.