@rodbv

Arquivo para janeiro 18th, 2009

Primeiros passos com Fluent NHibernate

com 3 comentários

Acredito que não sou o único que ficou “mal-acostumado”; com a facilidade trazida pela tecnologia Linq to SQL (L2S) que a Microsoft lançou no ano passado.

Infelizmente, há poucos meses a Microsoft anunciou baixinho que não iria mais desenvolver o mesmo L2S, e recomendou que todos adotassem em seu lugar Entity Framework, que, se por um lado é uma plataforma mais robusta, por outro lado é mais complexa, e, assim como L2S, atraiu muitas críticas de puristas que consideram essa plataforma difícil de estender e testar via unit tests.

Isso fez com que muitas empresas, incluindo a que eu trabalho, começasse a ver quais alternativas existem para O/RM, e de fato existe um produto no mercado que é livre e que já provou seu valor: NHibernate. Algo me diz que 2009 vai ser o ano que NHibernate vai entrar de fato na caixa de ferramentas da maioria dos desenvolvedores intermediários e avançados, ainda mais com o recente desenvolvimento da biblioteca Linq2NHibernate (L2NH) e Fluent NHibernate, esse último uma forma de configurar NHibernate usando código ao invés de arquivos XML.

Como nunca usei NHibernate nem L2NH, mas pretendo em muito breve, vou cobrir nesse artigo e nos próximos os primeiros passos com essa plataforma. Meu plano é usar esse código como base para um aplicativo ASP.NET MVC que também quero blogar aqui em forma de tutorial.

Pra dar uma visão melhor de onde quero chegar, o aplicativo que pretendo desenvolver é uma “Estante de livros virtual”, que me permita cadastrar livros (titulo, autor, imagem de capa etc) manualmente ou através de procura na Amazon, bem como marcar quais livros eu pretendo comprar, estou atualmente lendo e emprestei para alguém. Numa segunda fase esse aplicativo deverá suportar diferentes usuários. Ainda que eu não pretenta seguir práticas estritamente TDD, vou procurar criar unit tests para a maior parte do meu código.

Criando o modelo

Antes de mais nada vou iniciar uma solução nova, Bookshelf, com dois projetos: Bookshelf.Model e Bookshelf.Tests. O primeiro vai conter classes do tipo POCO (Plain Old C# Object), que significa classes que não têm qualquer conhecimento sobre qual plataforma é usada para armazenamento. Métodos como book.Update() e book.Delete() não vão existir aqui. Isso é uma forma de seguir o princípio de responsabilidade única, que mencionei num post anterior.

Solution1

O modelo inicial é extremamente simples: uma única classe, Book (vou escrever o código em inglês, incluindo nome de classes e comentários, pois vou compartilhá-lo com alguns colegas de trabalho noruegueses).

namespace Bookshelf.Model
{
    public class Book
    {
        public int ID { get; private set; }
        public string ISBN { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public int ReleaseYear { get; set; }
    }
} 

O próximo passo é mapear esse modelo, ou seja, ensinar ao NHibernate a acessar essa classe e como a mesma deverá ser representada no banco de dados. O NHibernate pode tanto mapear bancos de dados já existentes quanto criar novos bancos de dados.

namespace Bookshelf.Model.Mapping
{
    public class BookMap : ClassMap
    {
        public BookMap() 
        {
            Id(x => x.ID);
            Map(x => x.ISBN).CanNotBeNull();
            Map(x => x.Title).CanNotBeNull();
            Map(x => x.Author);
            Map(x => x.ReleaseYear);
        }
    }
}

Como o nome diz, dá pra ver que a leitura desse código é bem fluente, e todos os métodos são encadeáveis, podem ser colocados uns após os outros formando “frases”; ou uma DSL rudimentar. O único encadeamento que fiz por enquanto foi ao usar CanNotBeNull() para os campos que não quero que sejam nulos. Para que esse código compilasse, tive que adicionar duas referências: NHibernate.dll e FluentNHibernate.dll (todas DLLs necessárias estão incluídas no código-fonte, no final do artigo).

Testando o mapeamento

Agora já podemos testar esse mapeamento, pra ver se funciona mesmo. No meu projeto Bookshelf.Tests vou criar uma classe BookTests. Também vou precisar de uma classe FixtureBase que vai ser a base de todas minhas classes para testes, vai conter o código a ser reutilizado.

Vou começar por adicionar algumas referências: nunit.framework.dll, NHibernate.dll, FluentNHibernate.dll e FluentNHibernate.framework.dll. Nesse caso vou usar NUnit, mas qualquer outra biblioteca de testes poderia ser usada.


namespace Bookshelf.UnitTests
{
    public class FixtureBase where TModel : PersistenceModel, new()
    {
		protected SessionSource SessionSource { get; set; }
		protected ISession Session { get; private set; }

		[SetUp]
		public void SetupSession()
		{
			var cfg = new SQLiteConfiguration()
			                           .InMemory()
			                           .ShowSql();
			SessionSource = new SessionSource(cfg.ToProperties(), new TModel());
			Session = SessionSource.CreateSession();
			SessionSource.BuildSchema(Session);

			Session.Flush();
			Session.Clear();
		}

		[TearDown]
		public void TearDown()
		{
			Session.Close();
			Session.Dispose();
		}
	}
}

O mais interessante aqui é o método SetupSession(), no qual criamos um banco de dados SQLite que vai rodar na memória apenas. Isso significa que os testes serão extremamente rápidos. Ao usarmos SQLite nos testes não significa que nosso programa propriamente dito terá que usar SQLite também, se quisermos usar SQL Server ou Oracle basta usarmos uma configuração diferente, seja via XML, seja usando a sintaxe fluente parecida com a acima. O método ShowSql() fará que toda vez que um comando ao banco de dados seja executado, o SQL será mostrado no console.

Session é equivalente ao DataContext no Linq2SQL, é o objeto usado para disparar comandos no NHibernate.

Vamos agora dar uma olhada na nossa classe de teste:

namespace Bookshelf.UnitTests
{

public class BookTestModel : PersistenceModel
{
    public TestModel()
    {
        addMappingsFromAssembly(typeof(BookMap).Assembly);
    }
}

[TestFixture]
public class BookTests : FixtureBase
{
    [Test]
    public void Can_Insert_Books() 
    {
		//Arrange
		var book = new Book
		{
			ISBN = "123456",
			Author = "Isaac Asimov",
			Title = "Foundation Trilogy",
			ReleaseYear = 1950
		};

		//Act
		Session.Save(book);
		Session.Flush();

		//Assert
		Session.Clear();
		var dbBook = Session.Get(book.ID);
			Assert.AreNotSame(book, dbBook);
			Assert.AreEqual(book.ID, dbBook.ID);
			Assert.AreEqual(book.Title, dbBook.Title);
			Assert.AreEqual(book.ISBN, dbBook.ISBN);
			Assert.AreEqual(book.ReleaseYear, dbBook.ReleaseYear);
		}
	}
}

Na verdade temos duas classes aqui: a primeira, BookTestModel, serve apenas para aplicar o mapeamento que definimos em BookMap. Como a classe BookTests deriva de FixtureBase, o código de setup de FixtureBase será executado para configurar nosso banco de dados de acordo com o mapeamento de BookTestModel.

Finalmente, o teste insere um livro no banco de dados (lembrando que nesse caso é um banco de dados na memória, mas poderia muito bem ser um físico), e depois busca o livro do banco de dados e compara as propriedades. Assert.AreNotSame garante que não estamos nos referenciando ao mesmo livro na memória.

Hora de executar o teste!

TestResult

Perfeito! Repare que os comandos SQL também foram impressos.

Se quiser testar o código você mesmo, baixe aqui.

Num próximo artigo, vamos começar a usar Linq2NHibernate para criar a aplicação MVC.

Escrito por rodbv

18/01/2009 em 22:31

Publicado em NHibernate, Programação

Etiquetado com

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.