Sunday, December 8, 2013

Pragmatic Domain-Driven Design

There's a DDD, then there's a pragmatic DDD. When we say pragmatic, it means the application must not sacrifice performance


You wanted to get the count of the person's hobbies and you wanted your code to be performant.

public class Person
{
    public virtual int PersonId { get; set; }    
    public virtual string Name { get; set; }
    
    public virtual IList<Hobby> Hobbies { get; set; }
}


This is not DDD. DDD must encapsulate, if we access Hobbies count directly we "can't" add any further condition(say only count the active hobbies) on it and expect the code to be performant, see further comments below.

var p = session.Load<Person>(1);
Console.WriteLine("Count: {0}", p.Hobbies.Count());


This is DDD, intelligence are encapsulated by the domain model. And this is also performant, the list is not eagerly-loaded, the count is performed directly by the database

public virtual int FavoriteHobbiesCount
{
    get
    {
        // Thanks Extra Lazy! This is on PersonMapping:
        //    rel.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Extra);

        // With Extra Lazy, counting will be performed at the database-side instead of counting the in-memory objects
        return this.Hobbies.Count();
    }
}

// On Main()
var p = session.Load<Person>(1);
var count = p.FavoriteHobbiesCount;
Console.WriteLine("Count: {0}", count);    

Output:


NHibernate:
    SELECT
        person0_.person_id as person1_0_0_,
        person0_.first_name as first2_0_0_,
        person0_.last_name as last3_0_0_
    FROM
        person person0_
    WHERE
        person0_.person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]
NHibernate:
    SELECT
        count(favorite_hobby_id)
    FROM
        favorite_hobby
    WHERE
        person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]
Count: 10





However, that code is not future-proof, Extra Lazy won't work efficiently when you add a condition on the list. i.e., the collection will be eagerly-loaded when you add a condition on it.

public virtual int FavoriteHobbiesCount
{
    get
    {
        // Thanks Extra Lazy! This is on PersonMapping
        //     rel.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Extra);

        // Hobbies' items will be eagerly-loaded when we add a condition on its Count even we use Extra Lazy
        return this.Hobbies.Count(x => x.IsActive); 
    }
}

// On Main()
var p = session.Load<Person>(1);
var count = p.FavoriteHobbiesCount;
Console.WriteLine("Count: {0}", count);    

Output:

NHibernate:
    SELECT
        person0_.person_id as person1_0_0_,
        person0_.first_name as first2_0_0_,
        person0_.last_name as last3_0_0_
    FROM
        person person0_
    WHERE
        person0_.person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]
NHibernate:
    SELECT
        favoriteho0_.person_id as person2_1_,
        favoriteho0_.favorite_hobby_id as favorite1_1_,
        favoriteho0_.favorite_hobby_id as favorite1_1_0_,
        favoriteho0_.person_id as person2_1_0_,
        favoriteho0_.hobby as hobby1_0_,
        favoriteho0_.is_active as is4_1_0_
    FROM
        favorite_hobby favoriteho0_
    WHERE
        favoriteho0_.person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]
Count: 9


The Count(x => x.IsActive) happens on application-side only, instead of being run on database.


To fix the slow performance, we must directly query the database. Pass an IQueryable to Person:


public virtual int GetFavoriteActiveHobbiesCountFromQueryable(IQueryable<FavoriteHobby> fh)
{            
    return fh.Count(x => x.Person == this && x.IsActive);            
}

    
// On Main()
var p = session.Load<Person>(1);
var count = p.GetFavoriteActiveHobbiesCountFromQueryable(s.Query<FavoriteHobby>()); 
Console.WriteLine("Count: {0}", count);    


Output:

NHibernate:
    SELECT
        person0_.person_id as person1_0_0_,
        person0_.first_name as first2_0_0_,
        person0_.last_name as last3_0_0_
    FROM
        person person0_
    WHERE
        person0_.person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]
NHibernate:
    select
        cast(count(*) as int4) as col_0_0_
    from
        favorite_hobby favoriteho0_
    where
        favoriteho0_.person_id=:p0
        and favoriteho0_.is_active=TRUE;
    :p0 = 1 [Type: Int32 (0)]
Count: 9


However, you'll notice that even we don't access any of the property of Person, the Person model is still eagerly-loaded from database. NHibernate will eagerly-load the model when we access any of its properties/methods, regardless of being mapped or unmapped.


To really fix that slow performance, move the model's behavior to extension method:

public static class PersonMethodsWithPerformanceConcerns
{
    public static int GetActiveFavoriteHobbies(this Person p, IQueryable<FavoriteHobby> fh)
    {            
        Console.WriteLine("Extension method version");
        return fh.Count(x => x.Person == p && x.IsActive);
    }
}


// On Main()
var p = session.Load<Person>(1);
var count = p.GetActiveFavoriteHobbies(s.Query<FavoriteHobby>()); 
Console.WriteLine("Count: {0}", count);

Output:

Extension method version
NHibernate:
    select
        cast(count(*) as int4) as col_0_0_
    from
        favorite_hobby favoriteho0_
    where
        favoriteho0_.person_id=:p0
        and favoriteho0_.is_active=TRUE;
    :p0 = 1 [Type: Int32 (0)]
Count: 9


That's it, performance must not be compromised on the altar of DDD


Full code: https://github.com/MichaelBuen/TestAggregate


Update 2018-May-20

On NHibernate 5, the collection will not be eager-loaded anymore when adding a condition on collection. It will perform a real database query instead. Prior to 5, this:

public virtual int FavoriteHobbiesCount
{
    get
    {
        // Thanks Extra Lazy! This is on PersonMapping
        //     rel.Lazy(NHibernate.Mapping.ByCode.CollectionLazy.Extra);

        // Hobbies' items will be eagerly-loaded when we add a condition on its Count even we use Extra Lazy
        return this.Hobbies.Count(x => x.IsActive); 
    }
}

will run this query, and perform the Count on application instead.
NHibernate:
    SELECT
        favoriteho0_.person_id as person2_1_,
        favoriteho0_.favorite_hobby_id as favorite1_1_,
        favoriteho0_.favorite_hobby_id as favorite1_1_0_,
        favoriteho0_.person_id as person2_1_0_,
        favoriteho0_.hobby as hobby1_0_,
        favoriteho0_.is_active as is4_1_0_
    FROM
        favorite_hobby favoriteho0_
    WHERE
        favoriteho0_.person_id=:p0;
    :p0 = 1 [Type: Int32 (0)]


With NHibernate 5, just add AsQueryable() on an entity's collection property, and NHibernate will happily run the query from the database instead, even if there's a condition on collection's query.


public virtual int FavoriteHobbiesCount
{
    get
    {
        // Hobbies' items will not be be eagerly-loaded anymore on NHibernate 5 even when we add a condition on its Count.
        return this.Hobbies.AsQueryable().Count(x => x.IsActive); 
    }
}

The resulting query would be like:
NHibernate:
    SELECT
        cast(count(*) as int4) as col_0_0_
    FROM
        favorite_hobby favoriteho0_
    WHERE
        favoriteho0_.person_id=:p0
        and favoriteho0_.is_active=:p1;
    :p0 = 1 [Type: Int32 (0)], :p1 = true [Type: Boolean]


Happy Computing! ツ

Thursday, December 5, 2013

Let the API do the heavy lifting

Instead of concatenating directory and filename manually:

string dir = @"C:\Windows";
string filename = "notepad.exe";
Console.WriteLine(dir + @"\" + filename);


Use Path.Combine:

string dir = @"C:\Windows";
string filename = "notepad.exe";
Console.WriteLine(Path.Combine(dir, filename));


Output: C:\Windows\notepad.exe


Aside from it take care of the nuances of platform differences(slash for *nix-based systems (e.g. Mono on Linux), backslash for Windows-based), it also takes care of trailing slash/backslash of the directory


The output of the following is still: C:\Windows\notepad.exe

string dir = @"C:\Windows\";
string filename = "notepad.exe";
Console.WriteLine(Path.Combine(dir, filename));


C# on Unix: http://ideone.com/rTACSJ

C# on Windows: http://dotnetfiddle.net/kxVNW5



Happy Coding! ツ

Saturday, November 30, 2013

Typical NHibernate SessionFactory boilerplate

using DomainMapping.Mappings;

using NHibernate.Cfg;


using System.Linq;

using System.Collections.Generic;



namespace DomainMapping
{
    public static class Mapper
    {


        static NHibernate.ISessionFactory _sessionFactory = Mapper.BuildSessionFactory();


        // call this on production
        public static NHibernate.ISessionFactory SessionFactory
        {
            get { return _sessionFactory; }
        }


        // Call this on unit testing, so we can test caching on each test method independently
        public static NHibernate.ISessionFactory BuildSessionFactory(bool useUnitTest = false)
        {
            var mapper = new NHibernate.Mapping.ByCode.ModelMapper();

            mapper.AddMappings
                (
                    typeof(Mapper).Assembly.GetTypes()
                          .Where( x => x.BaseType.IsGenericType
                                       && x.BaseType.GetGenericTypeDefinition()==typeof(NHibernate.Mapping.ByCode.Conformist.ClassMapping<>)) 
                );

            // Or you can manually add the mappings
            // mapper.AddMappings(new[]
            //    {
            //        typeof(PersonMapping)
            //    });


            var cfg = new NHibernate.Cfg.Configuration();

            // .DatabaseIntegration! Y U EXTENSION METHOD?
            cfg.DataBaseIntegration(c =>
            {
                var cs = System.Configuration.ConfigurationManager.ConnectionStrings["TheJunBetConnection"].ConnectionString;

                // SQL Server
                c.Driver<NHibernate.Driver.SqlClientDriver>();
                c.Dialect<NHibernate.Dialect.MsSql2008Dialect>();
                c.ConnectionString = "Server=localhost;Database=TestTheDatabase;Trusted_Connection=True;MultipleActiveResultSets=True";

                // // PostgreSQL
                // c.Driver<NHibernate.Driver.NpgsqlDriver>();
                // c.Dialect<NHibernate.Dialect.PostgreSQLDialect>();
                // c.ConnectionString = "Server=localhost; Database=test_the_database; User=postgres; password=opensesame";                


                if (useUnitTest)
                {
                    c.LogSqlInConsole = true;
                    c.LogFormattedSql = true;
                }
            });

            var domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();


            // // AsString is an extension method from NHibernate.Mapping.ByCode:
            // string mappingXml = domainMapping.AsString();

            // // Life without Resharper. 
            // string mappingXml = NHibernate.Mapping.ByCode.MappingsExtensions.AsString(domainMapping);

            // System.Console.WriteLine(mappingXml);


            cfg.AddMapping(domainMapping);


            // http://www.ienablemuch.com/2013/06/multilingual-and-caching-on-nhibernate.html
            //var filterDef = new NHibernate.Engine.FilterDefinition("lf", /*default condition*/ null,
            //                                                       new Dictionary<string, NHibernate.Type.IType>
            //                                                           {
            //                                                               { "LanguageCultureCode", NHibernate.NHibernateUtil.String}
            //                                                           }, useManyToOne: false);
            //cfg.AddFilterDefinition(filterDef);



            cfg.Cache(x =>
            {
                // SysCache is not stable on unit testing
                if (!useUnitTest)
                {
                    x.Provider<NHibernate.Caches.SysCache.SysCacheProvider>();

                    // I don't know why SysCacheProvider is not stable on simultaneous unit testing, 
                    // might be SysCacheProvider is just giving one session factory, so simultaneous test see each other caches
                    // This solution doesn't work: http://stackoverflow.com/questions/700043/mstest-executing-all-my-tests-simultaneously-breaks-tests-what-to-do                    
                }
                else
                {
                    // This is more stable in unit testing
                    x.Provider<NHibernate.Cache.HashtableCacheProvider>();
                }





                // http://stackoverflow.com/questions/2365234/how-does-query-caching-improves-performance-in-nhibernate

                // Need to be explicitly turned on so the .Cacheable directive on Linq will work:                    
                x.UseQueryCache = true;
            });


            if (useUnitTest)
                cfg.SetInterceptor(new NHSqlInterceptor());

            var sf = cfg.BuildSessionFactory();




            //using (var file = new System.IO.FileStream(@"c:\x\ddl.txt",
            //       System.IO.FileMode.Create,
            //       System.IO.FileAccess.ReadWrite))
            //using (var sw = new System.IO.StreamWriter(file))
            //{
            //    new NHibernate.Tool.hbm2ddl.SchemaUpdate(cfg)
            //        .Execute(scriptAction: sw.Write, doUpdate: false);
            //}

            return sf;
        }

        class NHSqlInterceptor : NHibernate.EmptyInterceptor
        {
            // http://stackoverflow.com/questions/2134565/how-to-configure-fluent-nhibernate-to-output-queries-to-trace-or-debug-instead-o
            public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
            {

                Mapper.NHibernateSQL = sql.ToString();
                return sql;
            }

        }

        public static string NHibernateSQL { get; set; }


    } // Mapper



}



Happy Coding!

Friday, November 22, 2013

Add a condition on joined entities in NHibernate

This query is working, but the cross-cutting concern, e.g., localization, is cluttering the main Where clause

var x = 
    (from q in 
        from ps in session.Query<GetProductSold>()
        join pl in session.Query<ProductLanguage>() on ps.ProductId equals pl.ProductLanguageCompositeKey.ProductId
        select new { ps, pl }
    where q.pl.ProductLanguageCompositeKey.LanguageCode == languageCode
    select q).Cacheable();


We can convert that to following, however NHibernate doesn't support this yet:
var x = 
    (from q in 
         from ps in session.Query<GetProductSold>()
         join pl in session.Query<ProductLanguage>().Where(plc => plc.ProductLanguageCompositeKey.LanguageCode == languageCode) on ps.ProductId equals pl.ProductLanguageCompositeKey.ProductId
         select new { ps, pl }                         
     select q).Cacheable();


We have to do it this way:
var x =
    (from q in
         from ps in session.Query<GetProductSold>()
         join pl in session.Query<ProductLanguage>() on new { ps.ProductId, LanguageCode = languageCode } equals new { pl.ProductLanguageCompositeKey.ProductId, pl.ProductLanguageCompositeKey.LanguageCode }
         select new { ps, pl }                         
     select q).Cacheable();

Saturday, November 2, 2013

Managed code success stories

If you think managed code is slow, think twice

C++ :
million lines of code
40 developers
15 years
output: 10k trades per second

Managed code :
6k lines of code*
1 developer
3 months
output: 200k trades per second

http://stackoverflow.com/questions/4257659/c-sharp-versus-c-performance/19505716#19505716

* Wow! 6k lines of F# code (nope, I'm not cursing lol) only, my thesis is just almost that size. The realization that a 6k lines of code could deliver world-class solution to a world-class problem given using the right tool, just humbled the C++ programmer in me; likewise to my C# skills too


Thoughts on managed language vs C++ on the above success of managed code against C++ :

https://twitter.com/jonharrop/status/392415184045633536


Of course there are more success stories on C++ too :

http://www.computerworlduk.com/news/open-source/3260727/london-stock-exchange-in-historic-linux-go-live/

http://www.computerworlduk.com/in-depth/open-source/3246835/london-stock-exchange-linux-record-breaking-system-faces-new-challengers/


Happy Computing! ツ