Design Meeting Notes - June 7, 2012

Naming convention for static readonly and const fields

Decision:

  • All constants are pascal cased (e.g. PascalHasMoreHumps)
  • All fields are underscore camel cased (e.g. _humpLikeACamel)
  • Any public fields will be made into constants or encapsulated

How will we handle breaking changes in EF6?

When considering whether or not to make a breaking change in EF6 or later we will consider the overall customer experience, both long term and short term. In particular if the short-term impact of the change is small and the long term benefit is great, then we will take the change. A “small” short-term impact usually means one or more of the following:

  • The change will not affect people except in very corner cases
  • The change causes an immediate and easy-to-fix build break such that the chance of it causing a production bug are small
  • The change is really a bug fix and so fixing it will make more applications work correctly rather than break those that depend on the broken behavior

A breaking change that we are unlikely to take (without a flag to switch the new behavior on) would be one that breaks runtime behavior (rather than the build) in a subtle and/or difficult to fix way.

We are more able to take breaking changes in future versions of EF because we will not be releasing in-place updates and are making use of semantic versioning to signal significant breaking changes to consumers of our assemblies.

We will mark work items/bugs that result in changes breaking changes so that we can release breaking changes.

High-level ideas for using dependency injection with EF and specifics for the provider

Specific, current problem:

  • How can we get the EF provider from the config when the config has been overridden using DbContextInfo?
    • Many places that need the provider are not coupled to DbContext or DbContextInfo
    • Adding coupling to the context due to the dependency on the provider smells bad

More general problem:

  • We need a way to resolve dependencies (such as the EF provider) such that setting how the dependency is resolved is decoupled from uses of the dependency
  • In other words, we need an inversion-of-control (IOC) or dependency injection (DI) container

High level design:

  • We don’t want to be strongly coupled to any one DI container
    • Don’t want the binary dependency on the DI assembly
    • Don’t want to limit people to using a specific DI container when they may already be using and/or prefer another
  • We can follow the MVC model of having an IDependencyResolver interface into which other DI containers can be plugged
    • Learn lessons from MVC—for example, provider a way to release dependencies using the Release method
    • We need the ability to resolve by both CLR type and name—for example, the provider invariant name
    • it might look something like:
    public interface IDbDependencyResolver
    {
        object Get(Type type, string name);
        void Release(object service);
    }
  •  
    • Open questions following the design meeting:
      • Do we want additional overloads of Get that take just the type and/or just the name?
      • How does taking the type and the name work when plugging in various DI containers?
    • Note that we will provide generic extension methods to avoid the need to cast.
  • Provider an  app domain wide registration point for the IDependencyResolver instance to use
    • We will set a default resolver that is used if you don’t know/want/need to use your own container
    • Will use a Chain of Responsibility pattern to allow dependency resolution to be overridden per dependency
    • Open issue: we need to figure out how this effects design time scenarios and DbContextInfo
      • We should look at using attributed methods similar to those that ASP.NET use
      • We could use the equivalent of a configuration class like we have for migrations
      • Look at using ServiceLocator or the equivalent
    • API might be something like:
    public static class DbDependencyResolver
    {
        public static IDbDependencyResolver Root
        {
            get { ... }
        }

        public static void Add(IDbDependencyResolver resolver)
        {
            ...
        }
    }
  •  
    • We decided not to provide a setter for the Root. There is no real need to change the root as opposed to adding a new resolver to the chain and we can simplify the code that uses the chain if we always know that the last one in the chain will be our resolver—for example, we can make the assertion that some dependencies will always return a value and will not be null. 
    • Possibly we don’t need to expose Root but rather just expose methods—for now we will expose Root
    • Based on decision for DbContextInfo we will probably need a Remove method to remove a resolver from the chain.
  • Internally, we will change places that have hard-coded dependencies to allow their dependencies to be injected
    • Hard coded dependencies may be use of new or access to a Singleton
  • For public surface that implicitly uses a hard-coded dependency we will use the app-domain wide resolver
    • We will probably also provide public surface for the injected dependency
  • Depending on the scope of dependencies that part of the code needs we may choose to inject an IDbDependencyResolver or the contract for the specific dependency
    • Using IDbDependencyResolver allows multiple independent dependencies to be injected together and allows new dependencies to be added in the future without changing the API
    • Injecting the specific dependency is better where it specifically needed by the code in question

How this solves the specific problem:

  • DbContextInfo adds a new dependency resolver to the default chain. This updates the app-domain wide configuration to use dependencies from the specified config:
         DbDependencyResolver.Add(new DefaultDependencyResolver(_appConfig));
    
  • We also looked at not changing the app-domain but instead having it configured onto the context and then flowed through to everywhere we use it:
     var extendedResolver = new ResolverChain(DbDependencyResolver.Root);
     extendedResolver.Add(new DefaultDependencyResolver(_appConfig));
     context.Resolver = extendedResolver;
  • The former has the advantages of simplicity for most of the stack and consistency for all the code no matter where it gets the root resolver from
  • The latter has the advantage that the DbContextInfo only sets the new resolver for the scope that it is in use for without changing the whole app-domain. However, uses of DbContextInfo are currently very limited and most based around app-domain modification anyway. We will provide a way for the DbContextInfo to remove its modification to the app-domain so that the changes can be scoped if needed.

Last edited Dec 14, 2012 at 6:29 PM by ajcvickers, version 2

Comments

No comments yet.