Posted
almost 15 years
ago
by
Rinat Abdullin
This is the next article in the Learning series for Lokad.CQRS Guidance. In the previous tutorial we've talked a little bit about Lokad.CQRS App Engine flexibility, while exploring Scheduled Tasks Feature and multiple message dispatching in the
... [More]
Sample 02. In this article we'll explore NHibernate Module for working with relational databases in the cloud.
Relational Persistence - SQL Azure and NHibernate Command-Query Responsibility Segregation concept does not imply any specific type of persistence to be used. It is persistence-ignorant and you can leverage various types of storage (often combining them for the greater benefits), while staying rather scalable. For example, you can frequently see that Udi Dahan uses SQL DB persistence in his samples, while Greg Young sometimes advocates Event Sourcing to avoid maintaining parallel realities. Both approaches are valid as a few other options. Choice depends strictly on your project specifics. As you probably already know, Windows Azure Platform, among over services, offers SQL Azure - reliable and cloud-based relational database. It is offered as a service and provides highly-scalable, reliable database that does not require any management. In order to work with such SQL database efficiently, one might need to use Object-Relational Mapper (ORM). There are multiple open-source choices out there. My favorite at the moment is NHibernate. It works really well with SQL Azure, as proven by production usage. Yet, initial application setup often takes a bit of time. You need to get the proper libraries, helper frameworks, wire everything in your IoC Container and keep in mind primary best practices for NHibernate and ORM. To make things simpler, Lokad CQRS features NHibernate Module which already encapsulates some patterns and practices. It is based on experience of working with this ORM in Windows Azure applications of Lokad (using it with local databases and SQL Azure). In a sense, this module is just a merged package of a few existing assemblies, that includes:
Core NHibernate Assemblies
Fluent NHibernate
LINQ for NHibernate
Session management wiring for Lokad.CQRS
As you can see, we enormously benefit from the other open source projects here. Additionally integration into the Lokad.CQRS App Engine enforces a few really important guidances on using relational storages for building CQRS applications. While doing that, the code stays rather simple and concise. For example, instead of this code: using (var session = _factory.OpenSession())
using (var tx = session.BeginTransaction())
{ var solution = session.Load<SolutionEntity>(solutionId); solution.State = SolutionEntityState.Ready; session.Save(solution); tx.Commit();
}
we can write this equivalent in our CQRS message handlers and scheduled task implementations: var solution = session.Load<SolutionEntity>(solutionId);
solution.State = SolutionEntityState.Ready;
You can jump directly to the module description in Lokad.CQRS or proceed reading below, to explore all these features at work in Sample 03. By the way, NHibernate module on it's own is also an example of extending configuration syntax of Lokad.CQRS builders. It could actually be used outside of the project to extend your Autofac-powered solution.
Lokad CQRS Sample 03 - Account Balance Check out Lokad.CQRS Learning Guidance for info on getting source code for the samples.
To keep things simple for you, Lokad CQRS Sample 03 uses NHibernate with SQLite DB engine. If you ever used Mozilla Firefox, Thunderbird, Skype or Apple iOS, then you've already been benefiting from SQLite (which is even used on the embedded devices, starting from various smart-phones and up to guided missile destroyer ships) This sample should work out-of-the-box in x64 OS (this also applies to the Windows Azure Fabric deployments, which are x64 bit). If you have x86 development OS, please go to the Samples/Library folder and replace SQLite.dll with SQLite.x86.dll version (found in the same folder). Keep in mind, that SQLite is used here for the learning simplicity. In this case it works for us. Yet, it real-world Windows Azure applications SQL Azure should be used instead for equivalent scenarios (this is achieved by just referencing different database engine in the config). SQLite will be reserved for the extreme data-processing cases, where SQL Azure just does not have enough performance or scalability.
Alternatively, you can reconfigure the sample to use SQLExpress database or SQL Azure instance in the cloud (Fluent NHibernate syntax reference). Basically, this sample does the following:
plugs and configures NHibernate with SQLite;
creates the database schema;
starts creating new accounts with zero balance and sending out bonus messages;
each bonus message adds some money to the account.
In order to use NHibernate with SQLite databases we need to reference Lokad.Cqrs.Nhibernate and original SQLite dll for the platform (already done in the sample). Then we configure our App Engine host for Windows Azure Worker Role. Configuration is similar to the previous samples and has only a single new snippet within the builder syntax: .WithNHibernate(m => m.WithConfiguration("MyDbFile", BuildNHibernateConfig))
Where BuildNHibernateConfig is a function that generates new configuration, given the connection string retrieved from the app config files. In our case that's SQLite. static Configuration BuildNHibernateConfig(string fileName)
{ // your automapping setup here var autoMap = AutoMap .AssemblyOf<AccountEntity>(type => type.Name.EndsWith("Entity")); // we use SQLite database that is kept in file and recreated on startup // and is created on start-up return Fluently.Configure() // use SQLite file .Database(SQLiteConfiguration.Standard.UsingFile(fileName)) // Generate automappings .Mappings(m => m.AutoMappings.Add(autoMap)) // regenerate database on startup .ExposeConfiguration(cfg => new SchemaExport(cfg).Execute(false, true, false)) .BuildConfiguration();
}
We are heavily using FluentNHibernate extensions here. Actual value for the connection string is pulled from "MyDbFile" setting of WindowsAzure worker role: Persistence classes are extremely simple to write on their own (and they are enough to generate all the tables and mappings): public class AccountEntity
{ public virtual Guid Id { get; private set; } public virtual IList<BalanceEntity> Balance { get; private set; } public AccountEntity() { Balance = new List<BalanceEntity>(); }
} public class BalanceEntity
{ public virtual AccountEntity Account { get; set; } public virtual long Id { get; private set; } public virtual string Name { get; set; } public virtual decimal Change { get; set; } public virtual decimal Total { get; set; }
}
NHibernate Module for Lokad.CQRS will actually do the rest, providing us with with an instance of ISession that we can use in our message handlers and scheduled tasks. In doing that it'll work closely with the transaction scopes (flushing session on commits), IoC lifetime scopes of Autofac and all the other details that we don't really want to bother about. Let's just use all this. Scheduled Task will create an account from time to time, sending message afterwards. It can get hold of NHibernate session by simply asking for it in the constructor. public sealed class CreateAccountFromTimeToTime : IScheduledTask
{ readonly ISession _session; readonly IMessageClient _client; public CreateAccountFromTimeToTime(ISession session, IMessageClient client) { _session = session; _client = client; }
Actual implementation is similar to any other usage sample of NHibernate. We create account and add an initial balance entry to it: public TimeSpan Execute()
{ // create new account var account = new AccountEntity(); _session.Save(account); // add initial balance of 0 to account var balanceEntity = new BalanceEntity() { Change = 0, Total = 0, Name = "New balance", Account = account }; _session.Save(balanceEntity); Trace.WriteLine("Created account " + account.Id.ToReadable()); _client.Send(new AddSomeBonusMessage(account.Id)); // sleep till the next run return 10.Seconds();
}
Let's explore the main code of AddSomeBonus handler, which highlights a few important things. When message arrives, we look up the last balance entry for the account. We'll be using strongly-typed LINQ for NHibernate, which will generate efficient SQL for execution on the server: public void Consume(AddSomeBonusMessage message)
{ // we are using LINQ for NHibernate here var balance = _session .Linq<BalanceEntity>() .Where(e => e.Account.Id == message.AccountId) .OrderByDescending(e => e.Id) .Take(1) .FirstOrDefault();
If the current balance is more than 50, we exit the handler without invoking any actions, otherwise - add new bonus and send another message: if (balance.Total > 50)
{ return;
} var total = balance.Total + 10;
var bonus = new BalanceEntity() { Account = balance.Account, Change = 10, Name = "Bonus", Total = total }; _session.Save(bonus);
_client.Send(new AddSomeBonusMessage(message.AccountId));
Now here is the interesting part. If something unexpected happens at any location within the command handling code (i.e.: database deadlock), then:
nothing will be committed to the database;
any message that we've sent, will not be pushed to the queue;
original message will show up in the queue for another processing attempt.
Basically, since we use messaging, retry-on-exception action policies are implicitly applied to all our CQRS command handlers. They are atomic. If the message handler fails more than 4 times (current default value), then the message is automatically moved to the poison queue (in this case: "sample-03-poison"), where it could be investigated further. No exceptions are lost. Further Reading:
NHibernate Module for Lokad.CQRS
Back to the Learning Guidance and Samples
Summary This was another tutorial from Lokad.CQRS Guidance Guidance. We've covered NHibernate Module, that integrates seamlessly with the App Engine infrastructure to simplify work with the relational databases. This essential material brings us one step closer to developing simple CQRS application with Web UI and ORM persistence managed by the Lokad CQRS App Engine. Stay tuned to the updates!
[Less]
|