this is a blog by Jeff Perrin, a software developer based in calgary alberta

Fun With Generics and Anonymous Methods

Here's a little snippet of pure C# 2.0 sex that shows how to use anonymous methods and generics to create a facade into your domain layer. But first, a little background...

Layers are as follows, top down:

  • UI layer. Utilizes DTO's assembled from the domain that directly map to a screen. All the screen does is display the values from the dto, nothing else. Testing the DTO's gets you really close to the skin of the UI.
  • Facade. Organizes calls to the domain along functional lines. Wraps calls to the domain in a transaction, catches errors and warnings and sticks them somewhere accessible to the UI. Assembles domain objects into DTO's for the UI. Updates the domain with info from the UI.
  • Domain. Business objects that perform the reall meat and hold all the logic pertaining to the customer's problem.

In my case, the domain is mapped to a database using NHibernate. I'm using a Repository object to abstract that stuff away. Originally, my facade directly used the Repository in the following pattern:

public void AddBasePackageOptionsToCart(int unitId, int basePackageId, DateTime currentDate)

        {

            using (IRepository repository = DataManager.StartTransaction())

            {

                Unit unit = _FindValidUnitById(unitId);

                BasePackage basePackage = unit.Floorplan.GetBasePackage(basePackageId, currentDate);

                unit.Cart.SelectBasePackageAndAddOptions(basePackage);

                repository.VoteToCommit();

            }

        }

This is nice and clear, althought what do we do if an exception is thrown? Wrap each and every call in a try/catch? This is ugly, and creates repetition. We also have to remember to commit the transaction every time we are using a read/write transaction. I've spent much time debugging only to find I forgot to call VoteToCommit(). We can do better.

Enter the FacadeTransaction object. Using generics and anonymous methods, we can make sure our call to VoteToCommit is always called. We can also centralize our exception handling in one single place. Witness:

internal class FacadeTransaction<T>

    {

        public delegate T TransactionMethod();

 

        public TransactionMethod method;

 

        public FacadeTransaction(TransactionMethod o)

        {

            method = o;

        }

 

        public T Execute()

        {

            using (IRepository repo = DataManager.StartTransaction())

            {

                T returnValue = method();

                repo.VoteToCommit();

                return returnValue;

            }

        }

 

        public T ExecuteReadOnly()

        {

            using (DataManager.StartReadOnlyTransaction())

            {

                return method();

            }

        }

    }

And here's how we utilize the FacadeTransaction:

public BasePackageListPageDto FindBasePackageListPageDto(int floorplanId, DateTime date)

        {

            return new FacadeTransaction<BasePackageListPageDto>(delegate

                {

                    Floorplan floorplan = Floorplan.FindById(floorplanId);

                    return new BasePackageListPageAssembler(floorplan, date).AssembleDto();

                }

                ).ExecuteReadOnly();

        }

 

        public BasePackageDto SaveBasePackage(BasePackageDto packageDto)

        {

            return new FacadeTransaction<BasePackageDto>(delegate

                {

                    BasePackageAssembler assembler = new BasePackageAssembler();

                    BasePackage package = assembler.Initialize(packageDto);

                    DataManager.GetCurrentRepository().Save(package);

                    return assembler.AssembleDto(package);

                }

                ).Execute();

        }

We use generics so that we can dynamically change our return type without casting. We use an anonymous method so that we can wrap each interaction with the domain in a single transaction without repeating code. We could also centralize our error handling if we wanted to. Sweet.

This Just In…

I Am the Hotness