This project is read-only.

Entity Modules Integration Sample

Component packaging technology

Admittedly, the development of data-connected applications is riddled with problems. In the area of business and LOB (Line-Of-Business) applications the problems are everywhere. The applications are overly complex, expensive, buggy and unreliable. Users hate them. Our users hate us - developers. Once the first version is built, the ongoing development of new features is an extremely painful process.
One can site several explanations for this poor state of affairs. In my opinion, the biggest problem is the fact that we failed to develop a component-packaging technology for database applications, similar to UI control libraries.
Those who remember it, the GUI technology really took off in the late 90's only after we had UI control libraries developed in OOP languages like C++ or c# that can be easily reused in any application. However, no similar packaging technology was created for cases when you have a database objects involved. Any "typical" application running over relational database contains many standard pieces like user logins, error logs, audit, authorization data, addresses, "people" tables, etc. Ideally these standard pieces could, and should have been developed independently, packaged and imported as components. That never happened. Some mini-solutions are shared only as verbal recipes in books and web forums. The trouble for packaging is the database-side objects - tables, relations, stored procedures. It is easy to package the c# code, but for database all we have is "edit this script and run it."

VITA framework provides a component packaging solution for data-connected applications.
  • VITA implements a robust method for importing the database schema - it is automatically created from entities definition. Schema follows the c# code. So you share c# library - and database objects will follow.
  • VITA provides a packaging technology for entities - Entity modules.

Each entity module is independently developed and tested. The application is assembled from a number of modules. There is one extra - modules can be customized and integrated together when assembled in the application. Some extra customization you may need to do:
  1. Extending the entities and adding some properties/columns that we need because of some custom requirements in our application
  2. The entities in one module may need to reference entities in other modules. At database level, it means we want some foreign key relationships between tables coming from different modules.
VITA uses a specially designed capability of entity modules - entity replacement. Once you instantiate the entity module, you can replace some entities defined inside with different entity types - extended original types or with entities from other modules. When the VITA activation creates an integrated model, it uses the replaced entities instead of the original ones everywhere. As a result, the database will contain tables for these new entities. Additionally, the programmer who assembles the application has some control over the way the database objects are constructed. The host application has the ability to control the style and aspects of ALL tables and objects created in the database - such as naming style, conventions, etc. So the tables coming from the imported module look and feel like native to the application - because they will follow the same conventions as the rest of the tables.
The following code demonstrates the concept. It is implemented as a unit test in the ModulesTest.cs file in the Vita.UnitTests.VsTest project.
The sample/test models a scenario when an application is built using several entity modules created by 3 programmers:
  • Alice creates the LoginModule. She creates the ILogin entity for storing login information, and creates a method for doing the login.
  • Bob creates the PersonModule. He defines the IPerson entity with numerous attributes that can be associated with a person.
  • Randy assembles the final application using modules from Alice and Bob. He extends ILogin and IPerson entities with his own versions, and "glues" the entities together - so the Login has a reference to the Person (user).
Here is how it goes.

LoginModule by Alice

Alice defines the ILogin entity to contain the login information for a user. She embeds an external reference to a User entity (that will be provided by a container application) by defining an empty interface IUserStub and using it as a type of ILogin.User property.

  namespace Alice {
    [Entity]
    public interface ILogin {
      [PrimaryKey, Auto]
      Guid Id { get; set; }
      string UserName { get; set; }
      int PasswordHash { get; set; }
      string FriendlyName { get; set; }
      IUserStub User { get; set; }
    }

    [Entity]
    public interface IUserStub { } // an empty stub to be replaced

    public class LoginModule : EntityModule {
      // Alice created a constructor with extra parameter userEntityType 
      // suggesting to provide a real type of User entity in the system.
      public LoginModule(EntityArea area, Type userEntityType) : base(area, "Login") {
        RegisterEntities(typeof(ILogin));
        ReplaceEntity(typeof(IUserStub), userEntityType);
      }

      //Alice defines a utility method to login users. 
      public static ILogin Login(IEntitySession session, string userName, 
                        int passwordHash) {
        // For simplicity, we use direct LINQ
        var query = from login in session.EntitySet<ILogin>()
                    where login.UserName == userName && 
                          login.PasswordHash == passwordHash
                    select login;
        var logins = query.ToList(); 
        return logins.Count == 0 ? null : logins[0];
      }
    }
  } 

PersonModule by Bob

Bob, working independently from Alice, creates a PersonModule that defines IPerson entity:

  namespace Bob {
    public enum Gender {
      Male,
      Female,
    }
    [Entity]
    public interface IPerson {
      [PrimaryKey, Auto]
      Guid Id { get; set; }
      string FirstName { get; set; } //default length would settings default, 32
      string LastName { get; set; }
      Gender Gender { get; set; }
    }

    public class PersonModule : EntityModule {
      public PersonModule(EntityArea area) : base(area, "Person") {
        RegisterEntities(typeof(IPerson));
      }

    }//class
  }//ns 

Integrated application by Randy

Randy wants to add two extra fields to Alice's and Bob's entities. He also wants Alice's ILogin entity to point to his (Randy's) IPerson entity which extends the Bob's IPerson.
Randy first defines new entities:

    using Gender = Bob.Gender;  //import enum definition

    [Entity]
    public interface IAppLogin : Alice.ILogin {
      [Unique]
      string EmployeeNumber { get; set; }
    }

    [Entity]
    public interface IPersonExt : Bob.IPerson, Alice.IUserStub {
      DateTime BirthDate { get; set; }
    }

The application setup code instantiates the modules, replaces the entities and creates the entity store:

        var setup = new EntityModelSetup("User");
        var area = setup.AddArea("user", "usr"); 
        var persModule = new Bob.PersonModule(area);
        var loginModule = new Alice.LoginModule(area, typeof(IPersonExt)); 
        // Now replace original entities with new interfaces; 
        // Alice's IUserStub is already replaced by Randy's IPersonExt.
        setup.ReplaceEntity(typeof(Alice.ILogin), typeof(IAppLogin), area);
        setup.ReplaceEntity(typeof(Bob.IPerson), typeof(IPersonExt), area);
        // Create entity store
        var entStore = TestSetupHelper.CreateEntityStore(setup, true, area);

Let's now use the code to create a person and a login:

        var session = entStore.OpenSession();
        var pers = session.NewEntity<IPersonExt>();
        pers.FirstName = "John";
        pers.LastName = "Dow";
        pers.BirthDate = new DateTime(1970, 5, 1);
        pers.Gender = Gender.Male; 
        var login = session.NewEntity<IAppLogin>();
        var loginId = login.Id; 
        login.User = pers;
        login.UserName = "johnd";
        login.FriendlyName = "JohnD";
        login.PasswordHash = 123;
        login.EmployeeNumber = "E111"; 
        session.SaveChanges(); 

        //Let's try to login the user we created using Alice's method
        session = entStore.OpenSession();
        var johnLogin = Alice.LoginModule.Login(session, "johnD", 123);
        Assert.IsNotNull(johnLogin, "Login failed"); 

Last edited Mar 18, 2012 at 8:41 PM by rivantsov, version 22

Comments

No comments yet.