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 ZamaroResumo
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.
- Entity Framework 4: Repositório GenéricoVisual Basic .NET
- As edições 14 da Easy .net Magazine e 88 da .net Magazine já estão disponíveis.ADO.NET
- Postando no Twiiter com .NET e Migre.meC#
- Setup ApplicationsVisual Basic .NET
- Problemas na manipulação de arquivos do MS Excel com .NETVisual Basic .NET