Desenvolvimento - Visual Basic .NET

Aplicações escalonáveis

O artigo trata de técnicas/conceitos para criar aplicações que podem ser expandidas através de plugins. Ilustrando como criar uma plataforma de software que permite agregar funcionalidades com desenvolvimento simplificado. A intenção do artigo é explanar como elaborar um software com técnicas refinadas visando o cliente que tem orçamento restrito para o desenvolvimento de aplicações que possam viabilizar melhorias em seus negócios com investimentos diluidos conforme a necessidade.

por Mauro Zamaro



Resumo

Há algum tempo eu imagino criar uma caixa de ferramentas para criação de sistemas pequenos e que possam ter seu valor agregado aumentado de forma simples. O custo de manutenção de sistemas é algo que assusta muitas empresas que produzem software e assusta ainda mais o cliente (o sujeito que compra os nossos softwares, lembra dele?).

Não adianta fazer uma bela casa se, quando a família aumentar, não houver possibilidade de aumentar um ou dois cômodos sem perder a “identidade” da casa ou, no pior caso, sem que seja necessário reconstruir a casa toda. Essse artigo inicia o desenvolvimento de uma base segura para o crescimento indeterminado de um software.

Introdução

Numa ocasião, um amigo me pediu que eu planejasse um pequeno software que fosse capaz de gerenciar sua padaria. Mas, como quase todos os donos de padaria, ele não possui capital suficiente para investir em um sistema que pudesse gerenciar TODOS os aspectos da padaria dele e, além disso, ele também não poderia esperar que um software desse porte ficasse pronto e funcionando em sua forma integral para iniciar o uso.

Minha agenda estava meio atribulada para resolver o problema do meu amigo. Mas isso me fez pensar sobre “como eu poderia ajudá-lo aos poucos”.

A melhor idéia que me veio à mente foi: Posso criar um Hospedeiro de pequenas aplicações independentes (uma para cada demanda) e vender essas pequenas aplicações separadamente à medida da necessidade do meu amigo.

Desenvolvimento

Eu preciso de um conector...

Ao ter essa idéia (não tão nova assim), a primeira coisa que resolvi fazer foi definir o desenho do meu conector para ligar a minha pequena aplicação ao seu hospedeiro.

Alguém aí já foi à França? Eu nunca fui, mas... Se precisasse ir provavelmente não poderia levar meu barbeador elétrico. O motivo é bem simples: as tomadas francesas têm um padrão diferente do que usamos no Brasil. Eu nem seria capaz de ligar meu barbeador elétrico em Paris. Provavelmente eu seria preso confundido com um terrorista no meu retorno ao Brasil, pois eu sou alérgico aos barbeadores com lâmina.

Como todo bom projeto de tomada que se preza, é necessário ter plugs complementares.

Assim, pensei para o hospedeiro (host) o seguinte conector.

Public Interface IPlugHost

ReadOnly Property PluggedComponents() As Hashtable

ReadOnly Property HostName() As String

ReadOnly Property MDIFormParent() As Windows.Forms.Form

End Interface

E para o pequeno aplicativo a ser acoplado o seguinte conector:

Public Interface IPlugable

ReadOnly Property [PluginName]() As String

Sub Initialize(ByRef MyHost As IPlugHost)

ReadOnly Property AvailableSubRoutines() As Hashtable

Sub ExecMain()

Sub ExecSub(ByVal strFunctionName As String, ByVal pParameters As Hashtable)

ReadOnly Property MainPluginForm() As Form

Property MainFormIsMDI() As Boolean

End Interface

Isolei essas duas interfaces em um componente separado (meu conector vai servir para outros projetos, lembra?). Chamei esse componente de IPlug.

Assim, posso desenvolver tanto o hospedeiro quanto a aplicação sabendo exatamente COMO integrar os dois de maneira bem simples.

O hospedeiro

Pensei em fazer um formulário MDI container que fosse capaz de agrupar as aplicações independentes em seu espaço de trabalho (o objetivo é simples: evitar aquele monte de janelas espalhadas que se alastram pelo desktop e fazem o usuário final perder o foco do que está fazendo).

Algo bem razoável seria como a imagem:

Ilustração 1- O formulário hospedeiro.

Chamei meu formulário de frmMain e informei-lhe de que ele seria meu conector hospedeiro da seguinte forma:

Public Class frmMain

Inherits System.Windows.Forms.Form

Implements IPlug.IPlugHost

É claro que eu tive de colocar uma referência ao componente IPlug no meu projeto.

Inseri na classe do meu formulário os seguinte membro privado:

#Region "Privatememnbers inserted"

Private mHashPlugins As Hashtable

#End Region

Esse Hashtable vai comportar TODOS os plugins que forem encontrados no diretório do aplicativo hospedeiro. Para garantir que não haverá problemas com o hashtable, no construtor da classe frmMain foi inserida também uma chamada ao construtor do hashtable (para evitar um possível NullReferenceException durante a leitura dos plugins).

mHashPlugins = New Hashtable

A implementação do conector IPlughost também foi bem traquila, usando sempre a metodologia KISS[1] como demonstrado abaixo:

Pregando o conector na parede.

#Region "IPlughost"

Public ReadOnly Property HostName() As String Implements IPlug.IPlugHost.HostName

Get

Return "GUI"

End Get

End Property

Public ReadOnly Property PluggedComponents() As System.Collections.Hashtable Implements IPlug.IPlugHost.PluggedComponents

Get

Return mHashPlugins

End Get

End Property

Public ReadOnly Property MDIFormParent() As System.Windows.Forms.Form Implements IPlug.IPlugHost.MDIFormParent

Get

Return Me

End Get

End Property

#End Region

Agora que o conector está instalado, resta ligar os fios que vão fazê-lo funcionar.

Verificando quais as aplicações externas disponíveis.

Ao iniciar o hospedeiro, ele deve verificar quais são as aplicações externas que ele deve comportar. Para isso, o melhor jeito é usar a leitura “dinâmica” das dlls disponíveis no diretório da aplicação e que sejam “acopláveis” ao hospedeiro.

Como nossa estratégia é bem simples e queremos isolar as tarefas o máximo possível, uma função bem adequada para verificar e iniciar as aplicações conectadas ao hospedeiro é a que segue:

Private Sub VerifyPlugins()

Dim strPlugins() As String

strPlugins = IO.Directory.GetFiles(Application.StartupPath, "*.dll")

For Each strPlugin As String In strPlugins

Dim fi As New IO.FileInfo(strPlugin)

If Not fi.Name.ToLower = "iplug.dll" Then

Dim sAssembly As System.Reflection.Assembly

sAssembly = System.Reflection.Assembly.LoadFrom(strPlugin)

Dim tp As Type

tp = sAssembly.GetType(fi.Name.Replace(".dll", "") & ".mainPlugin")

If Not tp Is Nothing Then

Dim objPlugin As Object

Try

objPlugin = Activator.CreateInstance(tp)

DirectCast(objPlugin, IPlug.IPlugable).Initialize(Me)

DirectCast(objPlugin, IPlug.IPlugable).MainFormIsMDI = True

If Not mHashPlugins.ContainsKey(DirectCast(objPlugin, IPlug.IPlugable).PluginName) Then

mHashPlugins.Add(DirectCast(objPlugin, IPlug.IPlugable).PluginName, objPlugin)

End If

Catch sme As System.MemberAccessException

"Do nothing, it"s the interface dll

End Try

End If

End If

Next

End Sub

Essa função merece um pouco de atenção agora, vamos detalha-la um pouco mais:

Dim strPlugins() As String

strPlugins = IO.Directory.GetFiles(Application.StartupPath, "*.dll")

Aqui eu pego a lista de todas as Dlls disponíveis no meu diretório do hospedeiro. Eu poderia usar um outro diretório como base para a pesquisa, mas resolvi deixar todos os executáveis juntos.

For Each strPlugin As String In strPlugins

Para cada dll que eu encontrar, vou verificar se é um plugin válido:

Dim fi As New IO.FileInfo(strPlugin)

Dim sAssembly As System.Reflection.Assembly

sAssembly = System.Reflection.Assembly.LoadFrom(strPlugin)

Dim tp As Type

tp = sAssembly.GetType(fi.Name.Replace(".dll", "") & ".mainPlugin")

Usando o Sytem.Reflection, é possível ler o conteúdo da dll e importar um tipo específico para um objeto do tipo Systsem.Type. Nesse caso, foi adotado o a classe mainPlugin como base para a entrada de uma aplicação externa que seja perfeitamente acoplada ao hospedeiro.

If Not tp Is Nothing Then

Se o Tipo foi encontrado:

Dim objPlugin As Object

Try

objPlugin = Activator.CreateInstance(tp)

Cria uma instância da classe

DirectCast(objPlugin, IPlug.IPlugable).Initialize(Me)

Chama a inicialização, acoplando o hospedeiro ao plugin

DirectCast(objPlugin, IPlug.IPlugable).MainFormIsMDI = True

If Not mHashPlugins.ContainsKey(DirectCast(objPlugin, IPlug.IPlugable).PluginName) Then

mHashPlugins.Add(DirectCast(objPlugin, IPlug.IPlugable).PluginName, objPlugin)

End If

Adiciona a aplicação externa ao hashtable de aplicações disponíveis. Cada aplicação será única no hashtable e, durante toda a thread do hospedeiro será usada a mesma instância da aplicação acoplada.

Instanciados todas as aplicações externas disponíveis, é hora de colocar um “item de menu” para o acesso padrão a cada uma dessas aplicações no meu formulário hospedeiro. Para isso, foi criada a seguinte função:

Private Sub CreateExecMenus()

Dim i As Integer = 1

For Each obj As IPlug.IPlugable In mHashPlugins.Values

Dim objMenu As New MenuItem

objMenu.Text = obj.PluginName

objMenu.Visible = True

Me.mnuPlugins.MenuItems.Add(objMenu)

AddHandler objMenu.Click, AddressOf Buttons_click

Next

End Sub

Para cada plugin inserido no hashtable, eu insiro os itens de menu e adiciono um disparador de eventos para acionar cada um dos meus aplicativos através do método “ExecMain()” da interface IPlugable.

Cada item de menu vai responder aos seguintes comandos:

Private Sub Buttons_click(ByVal sender As System.Object, ByVal e As System.EventArgs) "handler for the plugins menus

DirectCast(mHashPlugins(DirectCast(sender, MenuItem).Text), IPlug.IPlugable).ExecMain()

End Sub

Note que eu não sei especificamente o que cada plugin faz, a única coisa que eu sei nesse momento é, que se está disponível no hashtable é um objeto de uma classe que implementa a interface IPlug.IPlugable. Isso me dá uma liberdade muito grande para desenvolver diversos “applets” que rodem por cima do meu hospedeiro.

Bem... Agora que toda a “base” está bem definida e sólida. Precisamos testar o conceito para ver se funciona mesmo, assim...

O primeiro plugin a gente nunca esquece...[2]

Adicionamos à solução um novo “Class Library” chamado “MyFirstPlugin”. A esse projeto, adicionamos um formulário com um botão como segue:

Ilustração 2 - Formulário para o nosso pequeno plugin

Inserimos o singelo código ao botão “Get out” do nosso formulário:

Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click

Me.Close()

End Sub

E, por fim mas não menos importante, implementamos a seguinte classe à nosso projeto:

Imports System.Windows.Forms

Public Class mainPlugin "The class must have that name.

Implements IPlug.IPlugable

#Region "Private members"

Private mHashAvailableSub As Hashtable

Private mHost As IPlug.IPlugHost

Private mMainForm As Form

Private mMainFormIsMDI As Boolean = True

#End Region

#Region "IPlug.IPlugable implementations"

Public ReadOnly Property AvailableSubRoutines() As System.Collections.Hashtable Implements IPlug.IPlugable.AvailableSubRoutines

Get

Return mHashAvailableSub

End Get

End Property

Public Sub ExecMain() Implements IPlug.IPlugable.ExecMain

mMainForm = New frmPlugMain

If mMainFormIsMDI AndAlso Not mHost.MDIFormParent Is Nothing Then

mMainForm.MdiParent = mHost.MDIFormParent

mMainForm.Visible = False

End If

mMainForm.Show()

End Sub

Public Sub ExecSub(ByVal strFunctionName As String, ByVal pParameters As System.Collections.Hashtable) Implements IPlug.IPlugable.ExecSub

"TODO:

End Sub

Public Sub Initialize(ByRef MyHost As IPlug.IPlugHost) Implements IPlug.IPlugable.Initialize

mHashAvailableSub = New Hashtable

mHost = MyHost

End Sub

Public ReadOnly Property PluginName() As String Implements IPlug.IPlugable.PluginName

Get

Return "MyFirstPlugin"

End Get

End Property

Public Property MainFormIsMDI() As Boolean Implements IPlug.IPlugable.MainFormIsMDI

Get

Return mMainFormIsMDI

End Get

Set(ByVal Value As Boolean)

mMainFormIsMDI = Value

End Set

End Property

Public ReadOnly Property MainPluginForm() As System.Windows.Forms.Form Implements IPlug.IPlugable.MainPluginForm

Get

Return mMainForm

End Get

End Property

#End Region

End Class

Misturando tudo no mesmo balaio (nesse caso, o diretório do hospedeiro) teremos a cada execução do hospedeiro uma verificação de todos os plugins que poderão ser executados por nossa aplicação.

Conclusão

Parece pouco, mas, há muitos softwares desenvolvidos hoje que perdem sua validade pelo simples fato de não serem escalonáveis e por não permitirem uma manutenção rápida tanto no desenvolvimento quanto na distribuição. Esses softwares tendem a ser expelidos do mercado. Sobreviverão apenas os sistemas que podem ser agilmente adaptados às inúmeras mudanças das necessidades dos clientes.

Download do código fonte da solução.


[1] KISS é a abreviatura de Keep It Simple, Stupid!

[2] (parafraseando uma frase bem famosa dos comerciais de sutien!)

Mauro Zamaro

Mauro Zamaro - Mauro Zamaro foi apresentado à computação antes mesmo de saber qual era a diferença entre uma barata e uma centopéia. Atua hoje como Arquiteto de soluções na Programmer's Informática, Gold Partner Microsoft, situada em Campinas/SP.

É certificado em VB.NET (Windows application) desde 2003. É certificado também em C#2.0 MCPD - Windows application e MCTS - Web Applications desde 2008.

Tem se dedicado ultimamente ao assunto "Application Lifecycle Management" no blog "Technicians in a square-ball's world" (
http://maurozamaro.spaces.live.com).