Thursday, November 22, 2007 2:11 AM

Continuando a construção do nosso Windows Service vamos implementar um controller que será responsável por coordenar a interação entre a camada de negócios da minha aplicação e minhas classes utilitárias que irão permitir realizar as tarefas em background. Relembrando nossa evolução: na primeira parte do artigo implementamos nosso monitor de diretórios, em seguida foi a vez do executor de tarefas.

Antes de começar temos que fazer um pequeno refactoring. Eu tive o cuidado de isolar a interação com as API em classes utilitárias com o objetivo de tornar minha solução mais desacoplada e facilitar a sua manutenção. Portanto, o ideal seria que o controller conhecesse apenas interfaces, sem lidar diretamente com as classes utilitárias. A sorte é qualquer ferramenta de refactoring permite extrair a interface de uma classe. Em poucos cliques crio estas duas interfaces:

namespace WinServiceSample.Utils
{
    public interface IFolderWatcher
    {
        bool isWatching { get; }

        void WatchFolder(string folderPath, string filter, IProcessFile processFile);
        void StopWatching();
    }
}

namespace WinServiceSample.Utils
{
    public interface IBackgroundRunner
    {
        void Start();
        void Stop();
    }
}

Estas interfaces serão utilizadas na classe controller que irá coordenar o trabalho das classes utilitárias e sua comunicação com a camada de negócios e também com a infra-estrutura necessária para executar estas tarefas em background. A implementação da controller é simples e mínima, para atingir nossos objetivos. Podemos ver abaixo que a classe tem apenas dois métodos: Start, que “liga” os processos, e o Stop, que os interrompe. Porém, ainda tenho um problema, apesar de criado as interfaces acima, minha classe ainda acessa diretamente as classes concretas através do construtor.

public class WinSrvController
{
    private IBackgroundRunner backgroundRunner;
    IFolderWatcher folderWatcher;

    public void Start()
    {
        ITask task = new TaskToRun();
        //Dependencia da classe concreta BackgroundRunner
        backgroundRunner = new BackgroundRunner(500, task);

        backgroundRunner.Start();

        IProcessFile process = new ProcessFile();
        //Dependencia da classe concreta FolderWatcher
        folderWatcher = new FolderWatcher();
        folderWatcher.WatchFolder("", "", process);
    }

    public void Stop()
    {
        backgroundRunner.Stop();
        folderWatcher.StopWatching();
    }
}

Existem várias formas de eliminar esta dependência, eu vou optar por utilizar uma ferramenta de Dependency Injection (DI), também conhecidas como “lightweight containers”. Com uma ferramenta destas é possível abstrair a construção das classes concretas do seu código. Neste exemplo vou utilizar o Windsor Container, que é parte integrante do Castle Project. Para instalá-lo você pode tanto utilizar o MSI de instalação do projeto, como fazer o download dos binários e referenciá-los no projeto.

  • OBS.: O assunto da coluna Tools da 5ª edição da Mundo.Net são as ferramentas de dependency injection. Lá você pode ler com mais detalhes sobre os conceitos, o Windsor e StructureMap, uma ferramenta alternativa (o links desta coluna podem ser visto neste post).

Esta mudança é um pouco radical, principalmente para quem não tem experiência com DI’s. Então vamos um pouco mais devagar. O primeiro passo são as referências necessárias no projeto: Castle.Microkernel e Castle.Windsor.

  • OBS.: Criei uma pasta chamada ext, na raiz da solução, contendo todas as dll externas utilizadas.

Depois vou criar um container, objeto do tipo Castle.Windsor.WindsorContainer:

container = new WindsorContainer(new XmlInterpreter());

XmlInterpreter é o objeto responsável por ler as configurações do arquivo de configuração. É nele que vou declarar todas as dependências. Vamos definir alguns conceitos rapidamente: Componente, para o Windsor, é uma unidade de código reutilizável que implementa um Serviço. O Serviço é definido por uma interface. Na prática um de nossos serviços é o IFolderWatcher, enquanto que um componente que implementa este serviço é o FolderWatcher.

No arquivo de configurações vamos definir os componentes e serviços que queremos utilizar. Primeiro definimos uma configSection chamada “castle” e o config handler que irá tratar esta seção. Dentro desta seção definimos nossos componentes, para cada um deles preciso declarar um id único, um type, que é o tipo concreto, ou o componente, e, opcionalmente, um service, que é a interface que o componente implementa.

No caso do BackgroundRunner existe um complicador, o seu construtor recebe parâmetros. Por isto tenho que definir os valores dentro da tag “parameters”. O último parâmetro é um pouco mais complicado, pois é um objeto e não um tipo primitivo, como um inteiro. Para resolver isto declaro outro componente, task, do tipo ITask, e defino o parâmetro como ${task}. Em português claro, significa uma nova instância do componente task.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section
        name="castle"
        type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />
  </configSections>

  <castle>
    <components>
      <component
            id="folderWatcher"
            service="WinServiceSample.Utils.IFolderWatcher, Utils"
            type="WinServiceSample.Utils.FolderWatcher, Utils" />
      <component
            id="task"
            type="BackgroundServiceController.TaskToRun, BackgroundServiceController" />
      <component
            id="backgroundRunner"
            service="WinServiceSample.Utils.IBackgroundRunner, Utils"
            type="WinServiceSample.Utils.BackgroundRunner, Utils" >
        <parameters>
          <interval>500</interval>
          <task>${task}</task>
        </parameters>
      </component>
    </components>
  </castle>
</configuration>

Recapitulando, criei um container e, através do arquivo de configuração, defini os componentes que o container poderá instanciar. Finalmente altero minha classe para utilizar o container para criar as instâncias destes componentes. Ao invés de chamar o construtor da classe BackgroundRunner, solicito uma instância deste componente ao container desta forma:

backgroundRunner = container.Resolve<IBackgroundRunner>("backgroundRunner");

O id, que defini na configuração do Windsor, é utilizado aqui, para identificar qual componente queremos. A utilização do Windsor ainda traz mais um benefício, os parâmetros do construtor ficam no arquivo de configuração com isto fica mais fácil configurar o valor do intervalo e também desacoplo a construção do objeto ITask do código-fonte.

  • OBS.: A solução ficou tão boa que irei fazer um refactoring para que o FolderWatcher também tenha um construtor com os parâmetros necessários.

Por fim, como ficou nossa controller:

public class WinSrvController
{
    private IBackgroundRunner backgroundRunner;
    private IFolderWatcher folderWatcher;
    private readonly WindsorContainer container;

    public WinSrvController()
    {
        container = new WindsorContainer(new XmlInterpreter());
    }

    public void Start()
    {
        backgroundRunner = container.Resolve<IBackgroundRunner>("backgroundRunner");

        backgroundRunner.Start();

        IProcessFile process = new ProcessFile();

        folderWatcher = container.Resolve<IFolderWatcher>("folderWatcher");
        folderWatcher.WatchFolder("", "", process);
    }

    public void Stop()
    {
        backgroundRunner.Stop();
        folderWatcher.StopWatching();
    }
}

Ainda tive que implementar duas classes: uma implementação da ITask e outra da IProcessFile. No mundo real, estas classes são importantíssimas, pois serão elas que realmente irão conter as tarefas. No entanto, para o nosso exemplo, elas não são importantes, podem ser implementadas em qualquer namespace e/ou assembly, ainda mais que a responsabilidade de instanciar estas classes pode ser delegada ao Windsor.

Por último tenho que testar a minha controller. Como este post já está grande demais vou publicar este teste em um post separado.

Até a próxima.

O link para o código-fonte:

< Exemplos >

Comments

At 11/26/2007 9:59 AM, Paulo Quicoli said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Eduardo,

antes de qualquer coisa, parabéns pelo ótimo trabalho na Mundo .NET.
Esta sua série de posts sobre o windows service está demais... não só pelo windows service, mas pelas dicas de arquitetura que vc mostra.

Valeu!
At 11/27/2007 8:38 AM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 3 / 4

@Paulo

Valeu pelo apoio! Blogueiros, como nós, as vezes ficam publicando coisas sem noção se tem gente lendo e/ou gostando do que estamos falando. Feedback é sempre legal.

abs
At 12/2/2007 10:29 PM, Eduardo Costa said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Eduardo,

o material do seu blog é muito bom e o conteúdo dos posts é excelente. Eu já respondi algumas perguntas nos fóruns da MSDN Brasil linkando com artigos e textos seus. :)

Continue mantendo o bom trabalho! Essa série sobre Windows Forms ficou bem legal mesmo.

Abraço.
PS: com sorte, consigo te cumprimentar no Tech Ed.
At 12/3/2007 2:58 PM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Espero nos encontrar lá.

Estarei lá em alguns momentos, especialmente próximo ao horário das minhas palestras. Infelizmente, não poderei ficar durante todo o evento.
At 12/3/2007 2:59 PM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Essa foi feia: "Espero nos encontrar lá." Espero te encontrar lá
At 2/2/2008 1:08 PM, Marco de Souza said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Eduardo,

Parabéns pelo Blog, pelas dicas, e por toda ajuda que oferece a quem precisa.
Eu, por exemplo, venho de outra plataforma e este seu post de Windows Service em C#. Ajudou-me muuuuuuito.
Mas tenho uma dúvida aparentemente idiota, mas que não consegui achar a solução: Onde gravo este arquivo de configuração XML que foi implementado acima?

Obrigado!
At 2/3/2008 12:16 PM, Eduardo Miranda said:

# re: Criando um Windows Service em C# - Parte 3 / 4

Toda a configuração do Windsor deve ficar no arquivo de configuração da aplicação. No projeto do serviço você adiciona um novo item do tipo "application configuration file"

Este link explica melhor: http://www.howtogeek.com/howto/programming/how-to-add-a-configuration-file-to-your-windows-forms-application/
Post Comment
Title *
Name *
Email (never displayed)
Website
Comment * (Allowed tags: blockquote, a, strong, em, p, u, strike, super, sub, code)  
Please add 7 and 6 and type the answer here: