Mês: março 2009

Spring.NET – Parte 2 – Web Services

Objetivo

Na primeira parte do tutorial, falamos sobre a capacidade do Spring de “amarrar” as camadas da aplicação através de injeção de dependência.

Nessa parte vamos ver um recurso um pouco mais avançado do Spring que é a possibilidade de “exportar” a camada de negócio da nossa aplicação, seguindo os conceitos tão atuais de “SOA” (Service-Oriented Architecture). Utilizando o Spring, podemos simplesmente “expor” os serviços da nossa aplicação.

O Spring permite que os objetos de seu application context sejam exportados em WebServices (SOAP), .Net Remoting e COM+. Nesse tutorial, vamos falar somente sobre Web Services.

Se você ainda não acompanhou a primeira parte, volte lá https://ericlemes.com/2009/03/12/dotnet-spring-pt1/ e acompanhe. Vamos partir do mesmo exemplo para fazer a “parte 2”. No finalzinho do tutorial tem o código fonte para download.

Configurando o Spring.Net

Para podermos expor nossos Serviços como Web Services, precisamos em primeiro lugar configurar o “handler” do Spring, para que os requests com final “.asmx” (ou o que você quiser) passem para o Spring primeiro. Para isso, devemos adicionar os seguintes nós abaixo do

<add verb="*" path="*.asmx" type="Spring.Web.Services.WebServiceHandlerFactory, Spring.Web"/>

Verifique se o *.asmx já não está sendo direcionado para outro Handler. Por default ele é.

Em seguida, devemos adicionar dentro do contexto (abaixo do nó ) o seguinte nó:

      <object id="PedidoWebService" type="Spring.Web.Services.WebServiceExporter, Spring.Web" lazy-init="true">
        <property name="TargetName" value="PedidoBO" />
        <property name="Namespace" value="http://www.meudominio.com.br/WebServices" />
        <property name="Description" value="Spring.Net Web Service" />
      </object>

Essa configuração diz para o Spring.Net criar uma instância de nome “PedidoWebService” para um objeto do tipo WebServiceExporter. Este objeto recebe como “target” nosso PedidoBO. Em outras palavras, “exporte PedidoBO como um Web Service”.

Testando

Compile e execute a aplicação. Ela vai cair no “default.aspx”. Altere a URL no final para PedidoWebService.asmx.

Voilá! Seu WebService está pronto.

Mas… o que aconteceu aqui!?

Vamos tentar explicar a magia negra. É mais ou menos assim:

  • O usuário mandou um request para PedidoWebService.asmx.
  • Como existe um HTTP Handler configurado para o Spring, o request é direcionado para o WebServiceHandlerFactory.
  • Este por sua vez, usando os recursos de AOP do Spring, “fabrica” (faz em tempo de execução, provavelmente via System.Reflection.Emit) uma classe que anota o nosso PedidoBO com “WebMethod” e qualquer outra coisa necessária para que ele seja acessível via Web.
  • O resultado disso é um Web Service .Net tradicional, porém, criado via “configuração”. Sem uma linha de código.

Código Fonte

O código fonte do nosso exemplo pode ser baixado em http://ericlemes.wikidot.com/local–files/dotnet-spring-pt2/SpringParte2.zip.

Spring.NET – Parte 1 – Dependency Injection

Objetivo

O objetivo desta “série” de artigos sobre o Spring.Net é explicar em linhas gerais o que é o Spring e como a vida melhora usando ele nos seus projetos.

Por ser uma framework que implementa muitas coisas, vou abordá-la em partes. Basicamente a idéia é explicar o application context (container) que ajuda a implementar os patterns de injeção de dependência (Dependency Injection – DI ou Inversion of Control – IoC), depois explicar os ganhos com algumas coisas de AOP (Aspect Oriented Programming ou Programação Orientada a Aspectos) “prontas” no Spring e por último conceitos de AOP e como implementar aspectos no Spring.

Vamos por partes… é bastante coisa.

O que é o Spring.Net?

O Spring.Net é uma versão portada para .Net da popular biblioteca “Spring” para Java, existente já a alguns anos e utilizada largamente em Java.

O objetivo principal da biblioteca Spring é implementar uma “infra-estrutura” poderosa para a aplicação bem como estimular a aplicações que usam o Spring a adotarem patterns de arquitetura já consagrados (como Dependency Injection).

A biblioteca está dividida em vários módulos (tem outros, vou citar só os principais):

  • Spring.Core – IoC Container, implementa o pattern de injeção de dependência ou inversão de controle (IoC, Inversion of Control).
  • Spring.Aop – Framework para programação orientada a aspectos
  • Spring.Data – Implementa uma camada de acesso a dados com alguma funcionalidade sobre o tradicional ADO.Net e principalmente transações declarativas.
  • Spring.Web – Possibilidade de “injetar dependências” em páginas aspx, implementação de Web Services no modelo Spring e outros.

Por que usar Injeção de Dependência?

O pattern de injeção de dependência é uma forma de minimizar o acoplamento das camadas da aplicação. Hoje em dia fala-se muito de desenvolvimento em camadas, reuso, etc. Quer provar se a sua aplicação é realmente em camadas?

Responda para si mesmo a pergunta: Eu consigo trocar minha camada de acesso a dados escrita na mão em ADO.Net para uma escrita em NHibernate?

Se a resposta for não ou talvez, é bem provável que suas camadas ainda tenham um acoplamento muito alto. O pattern de injeção de dependência vem para ajudar a minimizar esse acoplamento.

A idéia central está sempre em programar contra interfaces, e as camadas só conhecerem classes de outras camadas através da interface. Ué… se é simples assim, pra que eu preciso do Spring então?

Se vc estiver instânciando essas classes manualmente na sua aplicação, vc continua fazendo os módulos terem interdependência entre si. Com o Spring é possível tornar todo esse processo de instanciar e “amarrar” as instâncias através de configuração, de forma que fica muito mais simples “trocar” as camadas, e forçar o baixo acoplamento na sua aplicação.

Na prática

Baixando o Spring

O Spring pode ser encontrado e baixado em http://www.springframework.net. A versão que usamos aqui é a 1.2.

Criando uma Web Application de Exemplo

Esse exemplo aqui não segue nenhum tipo de convenção, padrão de arquitetura, nomenclatura nem nada. Essa não é a preocupação desse artigo.

Uma série de assemblies serão necessários para ilustrar as “camadas” da aplicação. Faremos esse trabalho nesse primeiro artigo, mas esse trabalho será reutilizado nos posteriores.

Vá no seu Visual Studio e crie uma novo Web Site.

Model

Crie uma class library dentro da solution, vamos batizá-la de “Model” (modelo), para ilustrar o assembly que vai conter nossos VO’s (Value objects, objetos anêmicos, class custom, ou como quer que queiram chamar isso).

Dentro da class library “Model” vamos criar uma classe “ItemPedido”, com o seguinte conteúdo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Model {
  [Serializable]
  public class ItemPedido {
    private int _ItemPedidoID;
    public int ItemPedidoID{
      get { return _ItemPedidoID;}
      set { _ItemPedidoID = value;}
    }
    
    private int _PedidoID;
    public int PedidoID{
      get { return _PedidoID;}
      set { _PedidoID = value;}
    }
  
    private int _ProdutoID;
    public int ProdutoID{
      get { return _ProdutoID;}
      set { _ProdutoID = value;}      
    }
    
    private float _Quantidade;
    public float Quantidade{
      get { return _Quantidade;}
      set { _Quantidade = value;}
    }
    
    private float _PrecoUnitario;
    public float PrecoUnitario{
      get { return _PrecoUnitario;}
      set { _PrecoUnitario = value;}
    }            
  }
}

Dentro da nossa class library “Model”, vamos criar uma classe “Pedido”, com o seguinte conteúdo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Model {
  [Serializable]
  public class Pedido {
    private int _PedidoID;
    public int PedidoID{
      get { return _PedidoID;}
      set { _PedidoID = value;}
    }
    
    private int _ClienteID;
    public int ClienteID{
      get { return _ClienteID;}
      set { _ClienteID = value;}
    }
    
    private float _ValorTotal;
    public float ValorTotal{
      get{ return _ValorTotal;}
      set {_ValorTotal  = value;}
    }
    
    private List<ItemPedido> _Itens = new List<ItemPedido>();
    public List<ItemPedido> Itens {
      get { return _Itens;}
      set { _Itens = value;}
    }
     
  }
}

Essa classe representa uma “estrutura de dados” que vai trafegar entre as camadas. Deve ser a camada com menor número de dependências possíveis, pois será usada em todas as outras camadas da aplicação. Essa é a idéia de um “POCO” (Plain Old C# Object, ou “bom e velho objeto C#”), uma classe minimalista que consiga representar a estrutura de dados necessária para o modelo de negócio.

DAL – Camada de acesso a dados (Data Access Layer)

Vamos separar a implementação da nossa camada de acesso a dados em três assemblys: Um para interfaces, um para persistência em memória, outro para persistência em disco.

Interface

Criar uma nova class library na solution com o nome DAL.Interface. Dentro desse assembly criaremos uma interface chamada “IPedidoDAO” e outra chamada “IItemPedidoDAO”, com o seguinte código:

IPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace DAL.Interface {
  public interface IPedidoDAO {
    void inserirPedido(Pedido p);
    void alterarPedido(Pedido p);
    void excluirPedido(Pedido p);
    Pedido pegarPorID(int PedidoID);
  }
}

IItemPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace DAL.Interface {
  public interface IItemPedidoDAO {
    void InserirItemPedido(ItemPedido ip);
    void AlterarItemPedido(ItemPedido ip);
    void ExcluirItemPedido(ItemPedido ip);
    List<ItemPedido> LerPorPedidoID(int PedidoID);
  }
}

Como vemos, a interface descreve as operações de “CRUD” (Create, read, update e delete), para a tabela de forma “chapada”, ou seja, as assinaturas dos métodos necessárias para fazer a persistência e recuperação das informações.

Esse assembly precisa ter o “Model” referenciado, pois as estruturas “Pedido” e “ItemPedido” são usados aqui no nosso “contrato” com a camada de acesso a dados.

DAL.Memoria

Vamos criar um novo assembly DAL.Memoria. Esse assembly irá referenciar o assembly “Model” e o “DAL.Interface”, pois será uma implementação da nossa camada de acesso a dados com persistência em memória.

Esse assembly possui duas classes:

PedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using Model;

namespace DAL.Memoria {
  public class PedidoDAO: IPedidoDAO {
    #region IPedidoDAO Members
    
    private Dictionary<int, Pedido> _pedidos = new Dictionary<int,Pedido>();       

    public void inserirPedido(Model.Pedido p) {
      //Autoincremento
      int max = 0;
      foreach(KeyValuePair<int, Pedido> pair in _pedidos){
        if (pair.Key > max)
          max = pair.Key;
      }
      p.PedidoID = max + 1;      
      _pedidos.Add(p.PedidoID, p);
    }

    public void alterarPedido(Model.Pedido p) {
      if (!_pedidos.ContainsKey(p.PedidoID))
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());
            
      _pedidos[p.PedidoID] = p;
    }

    public void excluirPedido(Model.Pedido p) {
      if (!_pedidos.ContainsKey(p.PedidoID))
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());
        
      _pedidos.Remove(p.PedidoID);
    }

    public Model.Pedido pegarPorID(int PedidoID) {
      if (!_pedidos.ContainsKey(PedidoID))
        return null;
      
      return _pedidos[PedidoID];
    }

    #endregion
  }
}

ItemPedidoDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using Model;

namespace DAL.Memoria {
  public class ItemPedidoDAO : IItemPedidoDAO {
    #region IItemPedidoDAO Members
    
    private Dictionary<int, ItemPedido> _itens = new Dictionary<int, ItemPedido>();

    public void InserirItemPedido(Model.ItemPedido ip) {
      //Autoincremento
      int max = 0;
      foreach (KeyValuePair<int, ItemPedido> pair in _itens) {
        if (pair.Key > max)
          max = pair.Key;
      }
      ip.ItemPedidoID = max + 1;
      _itens.Add(ip.ItemPedidoID, ip);
    }

    public void AlterarItemPedido(Model.ItemPedido ip) {
      if (!_itens.ContainsKey(ip.ItemPedidoID))
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      _itens[ip.ItemPedidoID] = ip;
    }

    public void ExcluirItemPedido(Model.ItemPedido ip) {
      if (!_itens.ContainsKey(ip.ItemPedidoID))
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      _itens.Remove(ip.ItemPedidoID);
    }

    public List<Model.ItemPedido> LerPorPedidoID(int PedidoID) {
      List<ItemPedido> result = 
        (from KeyValuePair<int, ItemPedido> pair in _itens
         where pair.Value.PedidoID == PedidoID
         select pair.Value).ToList<ItemPedido>();
        
      return result;
    }

    #endregion
  }
}

Como vemos, essa implementação é bem “burra”. Nâo controla concorrência, absolutamente nada. Apenas mantemos uma instância de um “Dictionary” que guardam a lista de pedidos e itens de pedido instanciados. Não é o objetivo deste artigo traçar um “modelo” para classe de persistência. Apenas ilustrar o nosso exemplo de isolamento em camadas.

DAL.Session

Vamos criar outra implementação para nossa camada de acesso a dados. Este novo assembly DAL.Session que vamos criar possui referência para Model e DAL.Interface também.

Essa implementação é muito similiar à primeira, porém, invés de guardar “em memória”, vamos guardar na “Session”. No final das contas é persistência em memória do mesmo jeito, mas a idéia aqui é apenas mostrar uma segunda implementação para a mesma idéia (acesso a dados) que atende ao mesmo “contrato” estabelecido com nossas interfaces IPedidoDAO e IItemPedidoDAO.

PedidoSessionDAO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using System.Web;
using System.Web.SessionState;
using Model;

namespace DAL.Session {
  public class PedidoSessionDAO : IPedidoDAO {

    #region IPedidoDAO Members       
    
    public void inserirPedido(Model.Pedido p) {
      //Autoincremento
      int max = 0;      
      foreach (string k in HttpContext.Current.Session) {
        if (!k.StartsWith("Pedido_"))
          continue;
        Pedido ped = (Pedido)HttpContext.Current.Session[k];
        if (ped.PedidoID > max)
          max = ped.PedidoID;
      }      
      p.PedidoID = max + 1;

      HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] = p;
    }

    public void alterarPedido(Model.Pedido p) {
      if (HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] == null)
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());

      HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] = p;
    }

    public void excluirPedido(Model.Pedido p) {
      if (HttpContext.Current.Session["Pedido_" + p.PedidoID.ToString()] == null)
        throw new Exception("Não existe pedido com o ID " + p.PedidoID.ToString());

      HttpContext.Current.Session.Remove("Pedido_" + p.PedidoID.ToString());
    }

    public Model.Pedido pegarPorID(int PedidoID) {
      return (Pedido)HttpContext.Current.Session["Pedido_" + PedidoID.ToString()];                    
    }

    #endregion
  }
}

ItemPedidoSessionDAO.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL.Interface;
using System.Web.SessionState;
using System.Web;
using Model;

namespace DAL.Session {
  public class ItemPedidoSessionDAO : IItemPedidoDAO {
    #region IItemPedidoDAO Members    

    public void InserirItemPedido(Model.ItemPedido ip) {
      //Autoincremento
      int max = 0;
      foreach (string k in HttpContext.Current.Session.Keys) {
        if (!k.StartsWith("ItemPedido_"))
          continue;
        ItemPedido iped = (ItemPedido)HttpContext.Current.Session[k];
        if (iped.ItemPedidoID > max)
          max = iped.ItemPedidoID;
      }
      ip.ItemPedidoID = max + 1;

      HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] = ip;
    }

    public void AlterarItemPedido(Model.ItemPedido ip) {
      if (HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] == null)
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] = ip;
    }

    public void ExcluirItemPedido(Model.ItemPedido ip) {
      if (HttpContext.Current.Session["ItemPedido_" + ip.ItemPedidoID.ToString()] == null)
        throw new Exception("Não existe item de pedido com o ID " + ip.ItemPedidoID.ToString());

      HttpContext.Current.Session.Remove("ItemPedido_" + ip.ItemPedidoID.ToString());
    }

    public List<Model.ItemPedido> LerPorPedidoID(int PedidoID) {
      List<ItemPedido> result = new List<ItemPedido>();
      foreach (string k in HttpContext.Current.Session.Keys) {
        if (!k.StartsWith("ItemPedido_"))
          continue;

        ItemPedido ip = (ItemPedido)HttpContext.Current.Session[k];
        if (ip.PedidoID == PedidoID)
          result.Add(ip);
      }
      return result;
    }

    #endregion
  }
}

BLL

Agora vamos exemplificar uma camada “de negócio”. Uma camada que “consome” nossa camada de acesso a dados (que contém o comportamento simples de persistência) e adiciona alguma regra de negócio.

A mesma abordagem do DAL, será usada. Interface e Implementação.

BLL.Interface

Vamos criar o assembly BLL.Interface, mais uma class library que conterá a interface IPedidoBO. Esses serão os métodos que serão “consumidos” por nossa camada de apresentação.

Esse assembly precisa conhecer somente o “Model”. Não precisa conhecer nem a interface nem as implementações da nossa camada de negócio.

IPedidoBO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;

namespace BLL.Interface {
  public interface IPedidoBO {
    void inserirPedido(Pedido p);
    Pedido pegarPedidoPorID(int PedidoID);
  }
}

A idéia deste contrato é no método inserirPedido, consistir e gravar o pedido e seus itens e no método “pegarPedidoPorID” pegar um pedido da base com seus itens.

BLL.Implementation

Vamos criar outro assembly, para a “implementação” da nossa camada de negócio. Esse assembly por sua vez precisa referenciar o Model (pois precisa conhecer as estruturas de nossos VO’s), precisa conhecer BLL.Interface, a interface para nossa camada de negócio e DAL.Interface, nossa interface para a camada de negócio.

Note que na implementação da nossa camada de negócio não precisamos conhecer a implementação da nossa camada de acesso a dados. Apenas a interface

PedidoBO.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BLL.Interface;
using DAL.Interface;
using Model;

namespace BLL.Implementation {
  public class PedidoBO: IPedidoBO {
    #region IPedidoBO Members
    
    private IPedidoDAO _pedidoDAO;
    public IPedidoDAO pedidoDAO{
      set {_pedidoDAO = value;}
    }
    
    private IItemPedidoDAO _itemPedidoDAO;
    public IItemPedidoDAO itemPedidoDAO{
      set {_itemPedidoDAO = value;}
    }

    public void inserirPedido(Model.Pedido p) {
      //Calcula automaticamente o valor total do cabeçalho do pedido baseado nos itens. 
      //Só pra exemplificar uma regra de negócio.
      float valorTotal = 0;
      foreach(ItemPedido ip in p.Itens){
        valorTotal += (ip.Quantidade * ip.PrecoUnitario);
      }
      p.ValorTotal = valorTotal;
      
      //Persiste o pedido e seus itens.
      _pedidoDAO.inserirPedido(p);
      foreach(ItemPedido ip in p.Itens){
        ip.PedidoID = p.PedidoID; //Passa o ID com autoincremento para os filhos.
        _itemPedidoDAO.InserirItemPedido(ip);
      }
    }

    public Pedido pegarPedidoPorID(int PedidoID) {
      Pedido p = _pedidoDAO.pegarPorID(PedidoID);
      p.Itens = _itemPedidoDAO.LerPorPedidoID(p.PedidoID);
      return p;
    }

    #endregion
  }
}

Finalmente, a Web Application

Na nossa “Default.aspx”, vamos adicionar um label, “lblMensagem” para darmos algum feedback para nosso usuário, dois botões, btnSalvarPedido e btnRecuperarPedido e um text box txtPedidoID para que o usuário possa informar o ID do pedido. Fica mais ou menos assim:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Label runat="server" ID="lblMensagem" />
      <br />
      <asp:Button runat="server" id="btnSalvarPedido" OnClick="btnSalvarPedidoClick" Text="Salvar pedido" />
      <br />
      <asp:TextBox runat="server" ID="txtPedidoID" />
      <asp:Button runat="server" ID="btnRecuperarPedido" Text="Recuperar Pedido" OnClick="btnRecuperarPedidoClick" />
    </div>
    </form>
</body>
</html>

No code behind, Default.aspx.cs, vamos declarar uma propriedade para acessar nossa camada de negócio:

  private IPedidoBO _pedidoBO;
  public IPedidoBO pedidoBO{
    set {_pedidoBO = value;}
  }

Mais pra frente vamos explicar o que é isso.

Agora vamos escrever o código para salvar e recuperar o pedido, acessando nossos métodos de IPedidoBO:

protected void btnSalvarPedidoClick(object sender, EventArgs e) {
    Pedido p = new Pedido();
    p.ClienteID = 1;
    
    ItemPedido ip1 = new ItemPedido();
    ip1.PrecoUnitario = 5;
    ip1.Quantidade = 10;
    p.Itens.Add(ip1);
    
    ItemPedido ip2 = new ItemPedido();
    ip2.PrecoUnitario = 20;
    ip2.Quantidade = 12;
    p.Itens.Add(ip2);
    
    _pedidoBO.inserirPedido(p);
    lblMensagem.Text = "Pedido " + p.PedidoID.ToString() + " salvo com sucesso.";
  }
  
  protected void btnRecuperarPedidoClick(object sender, EventArgs e) {
    Pedido p = _pedidoBO.pegarPedidoPorID(Convert.ToInt32(txtPedidoID.Text));
    lblMensagem.Text = "Pedido " + 
      p.PedidoID.ToString() + " valor total " + 
      p.ValorTotal.ToString() + " possui " + p.Itens.Count.ToString() + " itens.";
  }

Nesse momento, se executarmos a aplicação e clicarmos no botão “Salvar Pedido” vamos perceber que não existe nada instanciado em _pedidoBO e tomamos um “NullReference” exception. É porque falta a configuração do “contexto” da aplicação. Agora entra o Spring.

Vamos alterar também o “pai” da nossa página. Ela não vai mais herdar de System.Web.UI.Page e sim de Spring.Web.UI.Page. Isso faz com que o Spring consiga “injetar” dependências na nossa página.

Configurando o contexto do Spring

A primeira coisa que temos que fazer é configurar o “application context”. O application context é um “pool” de objetos pré-instanciados no momento da subida da aplicação para que estes possam ser “injetados” em outros objetos/camadas (por isso o conceito de “injeção de dependência”).

Para configurar o Application Context, devemos adicionar a referência para o Spring.Core e o Spring.Web na nossa aplicação (elas estão no diretório de instalação do Spring, Arquivos de programas\Spring .Net 1.2)

Dentro do nó “configSections” da sua web.config, adicione os seguintes elementos:

<sectionGroup name="spring">
  <section name="context" type="Spring.Context.Support.WebContextHandler, Spring.Web"/>
  <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>

Isso está dizendo para sua aplicação que a Web.Config agora entende uma sessão “spring” de configuração. E dentro dessa sessão ela entende outras duas chamadas “context” e “objects”.

Dentro de , vamos adicionar o seguinte nó:

<add verb="*" path="*.aspx" type="Spring.Web.Support.PageHandlerFactory, Spring.Web"/>

Com isso, dizemos que todo request que terminar com “.aspx”, vai passar por esse “handler” do Spring (necessário para que seja possível aplicar injeção de dependência nas páginas).

Por último, dentro de , vamos adicionar o seguinte nó:

<add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>

Isso tbém tem a ver com configuração de injeção de dependência do Spring em páginas aspx.

Como filho do nó vamos adicionar os seguintes nós:

  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="PedidoDAO" type="DAL.Session.PedidoSessionDAO, DAL.Session" />
      <object id="ItemPedidoDAO" type="DAL.Session.ItemPedidoSessionDAO, DAL.Session" />
      <object id="PedidoBO" type="BLL.Implementation.PedidoBO, BLL.Implementation" autowire="byType" />
      <object type="~/Default.aspx" >
        <property name="pedidoBO" ref="PedidoBO" />
      </object>        
    </objects>
  </spring>

Aqui mora o “X” da questão. Vamos detalhar essa configuração.

  • O nó diz que a configuração dos objetos vai pra dentro do nó e . O Spring permite que os objetos a serem instanciados no contexto possam ser especificados em vários lugares (compilados como recursos em assembly, arquivos xml externos e outros).
  • Os nós abaixo de “” descrevem “instâncias de objetos nomeadas” que o Spring irá criar no momento da subida da aplicação Web. Aqui descrevemos o seguinte:
    • Na instância chamada PedidoDAO, Vamos criar um objeto do tipo PedidoSessionDAL, que vive no assembly DAL.Session (A sintaxe é sempre NomeDaClasse.Com.Namespace, NomeDoAssembly). O spring vai usar esse tipo para criar a instância via Reflection.
    • Na instância chamada ItemPedidoDAO, vai um objeto do tipo ItemPedidoSessionDAO.
    • Na instãncia chamada PedidoBO, vai um objeto do tipo PedidoBO. A cláusula “autowire byType”, significa que ao subir esse objeto no contexto, o Spring vai “automaticamente” varrer as propriedades públicas do nosso PedidoBO e, caso encontre algum objeto no contexto que implemente o tipo dessa propriedade, ele irá automaticamente “amarrar” a instância. Na prática, isso vai fazer com que as instâncias PedidoDAO e ItemPedidoDAO sejam automaticamente atribuídas no nosso PedidoBO.
    • Por último, dizemos que todos os objetos do tipo “~/Default.aspx”, terão sua propriedade pedidoBO, automaticamente atribuida para nossa instância chamada PedidoBO no contexto. Isso significa que sempre que cair um “request”, automaticamente o pedidoBO será “injetado” na nossa página aspx.

Aqui é que a brincadeira fica interessante. Se executarmos novamente nossa aplicação, vamos perceber que ao clicar no botão “Salvar Pedido”, a instância _pedidoBO automaticamente recebeu nossa instância “PedidoBO” e que as instâncias da camada de acesso a dados também foram atribuídas ao nosso PedidoBO, e com isso, magicamente nosso código funciona.

Sobre as referências, percebemos que, para “compilar” a aplicação, precisamos apenas do BLL.Interface e BLL.Model, pois são as únicas camadas que nosso aspx depende diretamente. Porém se subirmos a aplicação, vamos perceber que ela não executa, porque o Spring não consegue instanciar alguns tipos (a implementação do DAL e do BLL). Aí puxamos a referência para que ele consiga “subir” a aplicação.

Esse processo de “jogar a implementação” para dentro do diretório “final” da aplicação não necessariamente precisa ser feito “criando uma referência” (que força a cópia pro “bin” automaticamente). Se isso for feito via um processo de build automatizado por exemplo, nossa aplicação pode ser desenvolvida sem “conhecer” essas implementações (ter a referência), minimizando significativamente o acoplamento entre as camadas (nosso objetivo principal).

Conclusão

Somente para finalizarmos a idéia de “baixo acoplamento”, vamos fazer um último exercício. Vamos alterar os nossos nós que declaram os objetos PedidoDAO e ItemPedidoDAO para os seguintes:

      <object id="PedidoDAO" type="DAL.Memoria.PedidoDAO, DAL.Memoria" />
      <object id="ItemPedidoDAO" type="DAL.Memoria.ItemPedidoDAO, DAL.Memoria" />

Se executarmos a aplicação agora e fizermos algum debug, vamos perceber que via “configuração”, trocamos a implementação de nossa camada de acesso a dados. Se tivessemos feito implementação “SQLServer” e “Oracle”, invés de “Memoria” e “Session”, teríamos uma aplicação multi-banco. Interessante, né?

Por este exemplo conseguimos perceber que o Spring possui ferramentas poderosas para desenvolvermos toda a “configuração” das camadas da nossa aplicação com baixo esforço e sem “acoplarmos” assemblies desnecessários na nossa aplicação.

A idéia de programar contra interfaces sempre e “injetar as dependências” em tempo de execução, pode trazer ganho significativo no sentido de induzir nossos desenvolvedores a programarem código reusável.

Há quem diga que somente esse benefício não justifica o uso do Spring. Para essas pessoas peço que aguardem as próximas partes do tutorial sobre Spring. Quando falarmos de transações declarativas e AOP duvido que estas pessoas não conseguirão enxergar todos os benefícios da adoção desta belíssima framework.

O código fonte usado no exemplo está disponível em: http://ericlemes.wikidot.com/local–files/dotnet-spring-pt1/SpringParte1.zip.

Programação Orientada a Objetos – Conceitos

Objetivo

O objetivo deste artigo é descrever as diferenças entre linguagens orientadas a objetos (com foco em C#) em relação a outras linguagens, e os benefícios que a programação orientada a objetos pode trazer no dia-a-dia.

Muitas pessoas acreditam que se simplesmente usam “classes” em suas aplicações, programam orientado a objetivo. A idéia central desse artigo é mostrar outros recursos, principalmente herança, polimorfismo e abstração e como aplicá-los na prática.

Esse artigo também mostra como utilizando técnicas de OOP (Object-oriented programming ou programação orientada a objetos) é possível minimizar o acoplamento entre classes, tornando o software mais fácil de manter.

Linguagens Não-estruturadas, Estruturadas e Orientadas a Objeto

Introdução

O objetivo deste tópico é voltar um pouquinho na história das linguagens de programação e apresentar as diferenças entre os 3 tipos de linguagens. Baseadas nessas diferenças, dá pra entender porque as linguagens orientadas a objeto representam uma evolução enorme em relação às suas velhas ancestrais.

Linguagens Não-Estruturadas

As primeiras linguagens existentes (não sei exatamente a data, mas chuto ser lá pela década de 70) eram não-estruturadas.

Na prática, pra gente tentar entender de uma forma bem simples, caracteriza-se pelo não modularização (não dá pra quebrar um programa em functions, procedures, etc) e o uso do “GO TO” para conseguir controlar todo o fluxo de controle do programa.

Alguns exemplos seriam o basic de MS-DOS (sim, eu vivi esse tempo), a linguagem “batch” do MS-DOS (arquivos .bat), e o BASIC. Pra gente tentar entender na prática, vai um exemplo em batch:

@ECHO OFF
GOTO :Start
:Erro
ECHO Ocorreu um Erro!
GOTO End

:Start
TESTE
IF ERRORLEVEL 10
GOTO Erro

:End
@ECHO ON

No exemplo acima, o programa roda de modo “macarrônico” ou segue, começando e executando linha a linha. Ele começa e já manda um goto para :Start. Executa a linha TESTE (tenta rodar um executável ou .bat de nome TESTE) e caso receba um errorlevel 10 (código de retorno de erro do executável), vai para a seção :Erro (ou seja, começo do programa). Mostra a mensagem de erro e vai para a seção :End (final do programa).

Em resumo… pra debugar esse tipo de programa é necessário bastante coragem, e ele não é intuituivo. Depende da boa vontade do programador estruturá-lo.

Linguagens Estruturadas

As linguagens estruturadas caracterizam-se pela possibilidade de estruturar o programa em procedures e functions, o que já resolve grande parte do problema de estruturação do código.

Esse é o caso de linguagens como ANSI C (não C++), Pascal (não Object Pascal ou Delphi) e outras.

Muitas dessas linguagens permitem o uso de structs (C) ou records (pascal) que já permitem a definição de tipos complexos, muito similares às classes nas linguagens orientadas a objeto que vemos hoje.

Por isso existe uma polêmica enorme sobre VB6 ou mesmo ASP sobre a linguagem ser orientada a objeto ou não. O caso é que o VB permite a criação de “classes”, porém, não possui outros conceitos que caracterizam linguagens orientadas a objeto como herança e polimorfismo. O VB.Net sim é orientado a objeto.

Linguagens de programação Orientadas a Objeto

O que caracteriza uma linguagem orientada a objeto basicamente são os recursos de herança e polimorfismo. Nessas linguagens entram C#, VB.Net, Java, C++, SmallTalk, Object Pascal (Delphi) entre outras.

Mas o que são esses recursos que caracterizam linguagens orientadas a objeto?

  • Classes e Objetos: Basicamente esse conceito simplifica muito os problemas gerados principalmente no C por usar estruturas (structs) e ponteiros para estruturas. Quem já programou em C sabe do que estou falando. Basicamente, sempre que declaramos classes, montamos tipos complexos (que possuem vários atributos, métodos etc) passados sempre por referência. Estruturas são sempre passadas por valor (por default). Para entender isso melhor, ver o artigo Tipos Escalares e Tipos Complexos ou Tipos por Valor e Referência
  • Herança: É a possibilidade de definir uma classe que “herda” todas as características de alguma outra classe e implementa novas. Isso faz com que o desenvolvedor passe a pensar melhor em “abstrações” e em reuso de código.
  • Polimorfismo: Para você nunca mais errar a forma de pronunciar essa palavra difícil é só pensar em Poli + morfos. Poli = muitas, morfos = formas. O conceito significa “um objeto podendo ser visto de várias formas diferentes”.

Existem vários outros conceitos dentro de linguagens orientadas a objetos como sobrecarga, encapsulamento, interfaces e outros. Basicamente se os 3 acima forem “dominados” é muito mais fácil entender os demais.

Acoplamento

Como os conceitos de orientação a objetos podem minimizar o acoplamento no meu código?

Vamos pensar num código para “gravar” um pedido. E vamos pensar que temos mais de uma regra para validar o desconto dado pelo vendedor no pedido. Por exemplo: Um vendedor “A” pode dar até 10% do valor total do pedido. Um vendedor “B”, pode dar 20% do valor do total do pedido desde que não ultrapasse um valor de R$10000.

Num modelo tradicional de desenvolvimento, teríamos algo parecido com:


private void gravarPedido(PedidoVO pedido){
  if (!validarDesconto(pedido))
    throw new Exception("Desconto não permitido");
  gravarNaBase(pedido);
}

private bool validarDescontoPedido(PedidoVO pedido){
  if (pedido.vendedor.descontoPorPercentual){
    float valorDescontoMaximo = pedido.valorTotal * (pedido.vendedor.percentualMaximoDesconto / 100);
    if (pedido.valorDesconto > valorDescontoMaximo)
      return false; //Não pode!
  }
  if (pedido.vendedor.descontoPorPercentualEValorMaximo){
    float valorDescontoMaximo = pedido.valorTotal * (pedido.vendedor.percentualMaximoDesconto / 100);
    if (pedido.valorDesconto > valorDescontoMaximo || pedido.valorDesconto > pedido.vendedor.valorMaximoDesconto)
      return false; //Não pode!
  }
  return true;
}

No exemplo acima, o código de validação de desconto está “acoplado” ao código de gravação do pedido, ou seja, sempre que entrar uma regra nova de cálculo de desconto, eu tenho que mexer na gravação de pedido. Sempre que eu mexo na gravação do pedido, eu corro o risco de quebrar toda a funcionalidade de pedido, só por causa da implementação de uma nova regra de desconto.

No caso de uma implementação orientada a objetos, poderia pensar numa interface “IValidadorDesconto” que possui um único método validarDesconto(PedidoVO vo). Cada vendedor, possui a sua implementação para validar desconto, e o código ficaria com algo parecido com:

private void gravarPedido(PedidoVO pedido){
  if (!pedido.vendedor.validarDesconto(pedido))
    throw new Exception("Desconto não permitido");
  gravarNaBase(pedido);
}

Não se preocupe por enquanto em entender o que é a interface, ou como a implementação foi parar ali. A idéia aqui é apenas exemplificar uma mesma versão do código em que a regra de validação de desconto NÃO está acoplada com o processo de gravação do pedido.

Nesse caso, existem várias implementações de regra de validação de desconto, que implementam essa interface IValidadorDesconto. E nesse caso, se eu implementar uma nova regra, eu crio outra classe que implementa IValidadorDesconto e não preciso mexer no código de gravação do pedido.

Esse processo, diminui a complexidade e o tanto de coisas que as classes precisam fazer, consequentemente tornando-as mais simples, de fácil manutenção e menos sujeitas a erro.

Entendendo os principais conceitos de OOP

Daqui pra frente, é muito importante que o conteúdo do artigo Tipos Escalares e Tipos Complexos ou Tipos por Valor e Referência esteja bem dominado, senão fica um pouco complicado entender os conceitos de abstração/polimorfismo na prática.

Não vou “esmiuçar” os conceitos de visibilidade (public, private, protected, sealed), nem overloads e outras coisas. Existem toneladas de documentos sobre isso na internet e é bem fácil de entender desde que o conceito de herança e Polimorfismo esteja bem fixo (estes eu pretendo detalhar aqui). Se sentirem necessidade de complementar esse artigo com essas informações, deixem um comentário na página e assim que possível o farei.

Herança

O conceito de herança é bem simples. Aproveitar todo o comportamento de um ancestral e adicionar novo comportamento.

Ex.:

public class ClasseBase{
  private int _AtributoInt;
  public int AtributoInt{
    get { return _AtributoInt;}
    set { _AtributoInt = value; }
  }

  public virtual void fazerAlgo(){
    Console.WriteLine("ClasseBase: FizAlgo " + _AtributoInt.ToString());
  }

  public void outroMetodo(){
  }
}

public class ClasseFilha: ClasseBase{
  private string _AtributoString;
  public string AtributoString{
    get { return _AtributoString;}
    set { _AtributoString = value;}
  } 

  public override void fazerAlgo(){
    base.fazerAlgo();
    Console.WriteLine("ClasseFilha: FizAlgo " + _AtributoString);
  }
}

No exemplo acima, quando declaramos : ClasseBase é aqui que eu estou descrevendo “herança”. Lê-se ClasseFilha herda ClasseBase. Se eu for usar essa classe dentro de um método num botão, obterei o seguinte comportamento:

private void button1_Click(object sender, EventArgs e) {
  ClasseFilha c = new ClasseFilha(); 
  c.AtributoInt = 10; //Posso usar AtributoInt aqui porque herdei essa característica da ClasseBase
  c.AtributoString = "teste"; 
  c.outroMetodo(); //Esse método pode ser usado aqui porque herdei essa característica da ClasseBase.
  c.fazerAlgo()
  // O resultado dessa chamada será ClasseBase: FizAlgo 10 E ClasseFilha: FizAlgo teste, porque tanto o código
  // da classe base quanto da classe filha são executados devido à chamada de base.fazerAlgo().
}

Se tiver dúvidas do porque do “new”, veja o artigo: Tipos Escalares e Tipos Complexos ou Tipos por Valor e Referência.

Sempre que definimos um método “virtual” numa classe ancestral, significa que “esperamos” que os mesmos sejam herdados e tenham comportamento diferente em classes filhas, que podem ser implementados com um “override”. Esse recurso é justamente o que permite que as classes filhas funcionem como uma espécie de “plugin”.

Interfaces

Vou pegar o gancho pra falar um pouquinho de interfaces, justamente pq o comportamento dela é muito semelhante a uma classe que é “herdada”.

A interface pode ser entendida como um “contrato” entre uma ou mais classes. Quando uma determinada classe “implementa” uma interface (esse é o termo correto), automaticamente a classe é obrigada a implementar os métodos definidos na interface, passando a ser compatível com ela.

Uma interface não possui código, implementação, apenas definições de métodos e seus parâmetros. Ex.:

public interface ITeste{
  public int AtributoInt{
    get;
    set;
  }

  public void fazerAlgo();
}

public class TesteImplementacao: ITeste{
  private int _AtributoInt;
  public int AtributoInt{
    get { return _AtributoInt;}
    set { _AtributoInt = value;}
  } 

  public void fazerAlgo(){
    Console.WriteLine("Fiz algo");
  }
}

No exemplo acima, a definição da interface ITeste diz que para algo implementar ITeste, precisa ter um atributo chamado AtributoInt que obrigatoriamente precisa ter um get e um set (poderia especificar somente um dos dois, por exemplo) e também um método fazerAlgo com retorno void e sem parâmetros.

A partir do momento que a classe TesteImplementacao ganhou um “: ITeste” estou dizendo que ela “implementa” ITeste e obrigatoriamente tem que ter o atributo e os métodos especificados na interface.

Tá bom, e pra que serve isso?

Vamos para o conceito de polimorfismo que a coisa se explica melhor.

Polimorfismo

Como já dito anteriormente: Poli = várias, morfos = forma, ou seja, “Um objeto pode ser visto de várias formas”. Essa é a idéia do conceito que nos ajuda a nunca mais esquecer essa palavra feia.

Para simplificar o conceito, vamos pensar nas classes já definidas anteriormente, com o seguinte exemplo:

public class Teste3: Teste2, ITeste {

}

Estou definindo uma classe Teste3 que herda de Teste2 (possui todos seus atributos e métodos) e implementa ITeste.

Agora veremos o seguinte exemplo:

private void button1_Click(object sender, EventArgs e) {
  Teste3 t = new Teste3(); //Criando nova instância de teste3;
  ITeste i = t; //Operação permitida sim! Porque pelo conceito de Polimorfismo, t É ITeste ou seja, estou "vendo ITeste de outra forma".
  Teste2 t2 = t;//Operação permitida também. Mesma coisa, posso ver t como Teste2. Teste3 É teste2.
  object o = t; // Operação também permitida. Estou vendo t como "object". Como todo mundo automaticamente herda de object, o mesmo conceito se aplica.
}

No exemplo acima estou vendo a mesma instância de várias maneiras. No caso, quando vejo ela numa visibilidade menor, por exemplo,
olhando a instância de Teste3 como ITeste, não posso acessar o método “outroMetodo”, porém, qualquer operação que eu faça ali, estou fazendo na mesma instância.

Ex.:

private void button1_Click(object sender, EventArgs e) {
  Teste3 t = new Teste3(); //Criando nova instância de teste3;
  t.AtributoInt = 10;
  t.AtributoString = "teste";
  ITeste it = t;
  it.fazerAlgo();
}

O resultado da chamada acima para “fazerAlgo” será:

ClasseBase: FizAlgo 10
ClasseFilha: FizAlgo teste

Isso ocorre porque estou acessando a mesma instância “Teste3” de outra forma.

É possível também o sentido inverso, ou seja, dada uma instância de “TesteBase”, eu conseguir acessar um método da classe Teste3 (desde que a instância seja de Teste3). Nesse caso entra um “typecast”, ou seja, eu preciso dizer para o compilador “converter” o tipo antes de acessar o método. Ex.:

private void button1_Click(object sender, EventArgs e) {
  Teste3 t = new Teste3(); //Criando nova instância de teste3;
  t.AtributoInt = 10;
  t.AtributoString = "teste";

  ITeste it = t; // Fiz o acesso da instância de Teste3 como ITeste.
  Teste3 teste3 = (Teste3)it; // Agora estou fazendo um "typecast" de ITeste para Teste3.
  teste3.AtributoString = "mudei o valor da mesma instância ainda";
}
[

]

Na verdade, melhor que o “converter” que já coloquei entre aspas de propósito, pode ler-se como “o compilador olha para um espaço de memória de uma forma diferente”.

Nesse caso, quando eu faço o “typecast” na verdade eu estou acessando uma referência atualmente vista como ITeste para Teste3. O compilador aceita se eu passar outro tipo que também implementa ITeste como parâmetro e essa operação “pode” gerar erros em runtime (caso a instância existente na variável it não seja do tipo Teste3).

Tentando ser mais didático

A combinação desses conceitos, herança, interfaces e polimorfismo, pode permitir que a sua aplicação funcione com uma estrutura similar a “plugins”, minimizando bastante o acoplamento. O ganho disso está na manutenção da aplicação. Mexe-se menos nos núcleos, motores e partes críticas e mais nas implementações específicas.

Em diversos lugares na própria framework do .Net vemos isso. As páginas aspx entendem os componentes de uma forma “abstrata”, ou seja, como System.Web.UI.Control. Todo e qualquer server control (mesmo os user controls) herdam dessa classe. Dessa forma, System.Web.UI.Page conhece apenas System.Web.UI.Control. Ela não tem a menor idéia do que seja System.Web.UI.TextBox, System.Web.UI.ImageButton. Em resumo, eu crio novos componentes (plugins) sem precisar mexer na estrutura de WebForms do ASP.Net, desde que eu saiba entender como essa classe abstrata “Control” funciona e como ela chama seus métodos virtuais. Não é mágico?

Pra gente entender na prática como programar algo baseado nesse conceito, ou ainda exemplificar o conceito, vamos fazer uma analogia com dispositivos USB. Pense em USB como uma interface (e ela é na vida real). Uma interface bonita, um cabo pequeno, barato, tem hubs, extensões e um monte de outras coisas que “falam” USB.

Na prática, um computador sabe “entender” dispositivos de armazenamento USB. Qualquer coisa que eu ligue nele que seja um dispositivo de armazenamento, ele automaticamente “entende”. Pode ser um celular, um HD externo, um pen drive, uma máquina fotográfica. Tudo isso porque o computador sabe entender de uma forma “abstrata” o comportamento de um dispositivo de armazenamento. Pegar arquivo, ler arquivo, gravar arquivo, apagar arquivo, são possíveis implementações para essa interface. Desde que o dispositivo saiba “implementar” essa interface, o computador automaticamente sabe conversar com ela, mesmo que seja feito por outro fabricante e que ele não saiba nenhum detalhe da sua implementação.

Eu comprei recentemente um celular que pode ser sincronizado com o computador. Quando eu ligo o celular na porta USB, o mesmo celular tem duas “implementações” para USB. Uma que ele pode ser acessado como um telefone comum, onde eu posso sincronizar meus contatos, compromissos, notas, etc e outra em que ele pode ser visto como um “dispositivo de armazenamento USB”. Dessa forma, podemos dizer que o telefone celular é meio “polimórfico” (acabei de cunhar esse termo, não levem ele a sério). Ele é um dispositivo que implementa uma interface para ser visto como um celular no computador, outro como um dispositivo de armazenamento, da mesma forma que nossas classes podem ser vistas de mais de uma forma.

Olhando desta forma, podemos pensar na nossa aplicação do mesmo jeito. Como um “motor” ou um “núcleo” e as coisas “específicas” como implementações, minimizando completamente o acoplamento, facilitando testes, extensão e mtas outras coisas.

Podemos pensar num sistema de pedidos com uma interface para validar descontos, ou um sistema de integração com uma interface para processar arquivos e diversas implementações para tratar os diferentes layouts. A sacada é pensar: “Que problemas na minha aplicação dependem de abstrações e implementações específicas?”. Automaticamente surgem vários problemas que podem ser resolvidos dessa forma.