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: