Arquivo para categoria White Fox

Produtividade – Camada de Domínio – Revisão

Olá pessoal! Em 2009 (nossa!), eu escrevi uma série sobre produtividade, onde a ideia era colocar as técnicas que usamos no nosso dia a dia na White Fox para o desenvolvimento de sistemas. Embora os princípios continuem todos válidos, a tecnologia evolui. Assim, a ideia é fazer uma série atualizando cada uma destas postagens, de acordo com a linha que usamos atualmente.

A primeira delas é sobre a camada de domínio. Nesta camada, tivemos um amadurecimento de tecnologias que hoje tornam mais fácil a vida de quem precisa utilizar um ORM. No entanto, cresceram as dúvidas e questionamentos teóricos sobre que mecanismo utilizar para acessar dados. A linha do NoSQL ganha força e é o padrão para muitos tipos de sistema, especialmente agora, com um uso maior de sistemas baseados em PAAS como Azure ou Amazon.

Mesmo para os sistemas com domínio orientado à objeto e banco SQL, há vários questionamentos sobre se faz sentido o uso de um ORM. Existem várias pessoas que apontam os problemas de se tentar usar um (exemplo aqui) e nós mesmos já sofremos bastante com problemas oriundos deste mapeamento; recentemente fiz até um post sobre isto. Existem até aqueles que questionam o uso de OO em si, ou os que estão partindo para linguagens funcionais como o F#.

Mas, para quem usa bancos SQL, o uso de ORM ainda é a melhor alternativa. Claro que isto pode gerar problemas e definitivamente há casos em que é melhor não usar. Mas em termos de produtividade e manutenabilidade, ainda é a melhor opção. Temos usado consistentemente em todos nossos sistemas e, salvo algumas exceções em pequenas áreas, o resultado tem sido excelente.

Em termos de tecnologia, temos mais alternativas do que o nHibernate, que reinava absoluto em 2009. Vários pequenos frameworks como o nPoco tem um uso mais difundido. E o Entity Framework (EF) da Microsoft amadureceu e hoje compete de igual para igual com o nHibernate. Nossos sistemas usam predominantemente o nHibernate, até porque temos grandes sistemas em operação que começaram com ele. Mas, para novos sistemas, estamos preferindo o EF, principalmente por sua excelente integração com o LINQ do .NET. Utilizamos o database-first, com a declaração fluente de mapeamento e ainda usamos arquivos .TT para gerar todos os artefatos como repositórios, containers etc.

Quando o sistema é muito pequeno ou quando desempenho é um requisito especialmente severo, criamos uma versão de ORM que utiliza stored procedures diretamente. É claro que isto tem sérias restrições, mas para estes tipos de sistemas conseguimos otimizar removendo quase todas as camadas e utilizando o máximo poder do servidor SQL.

Para tentar minimizar a manutenção, criamos bibliotecas comuns a todos os ORM que utilizamos. Assim, trabalhar em sistemas que usam ORMs diferentes é uma experiência similar da camada de negócios para cima. Temos caso até de, em um mesmo sistema, termos diferentes áreas usando diferentes ORMs. Claro que nestes casos, temos que ter uma camada de comunicação para garantir integridade, muitas vezes baseadas em microservices.

Em termos de regras de negócio, continuamos utilizando um modelo anêmico. Esta continua sendo uma guerra santa na comunidade técnica, mas no nosso caso, não temos como negar todos estes anos de sucesso. O modelo anêmico facilita o isolamento de regras de negócio, simplifica o treinamento de novos desenvolvedores e a manutenção de todos os nossos sistemas. E estamos ainda colhendo alguns bônus adicionais, pois este tipo de modelo tem facilitado o isolamento de porções de regras de negócio para encapsulamento em micro-serviços, o que nos tem permitido evoluir nossos sistemas mais antigos de maneira viável. E, na inevitável migração para a nuvem, que deve ocorrer ao longo dos próximos anos, vamos poder também fazer uso de recursos avançados como Azure Servless Functions, graças a esta arquitetura.

Nos próximos posts devo comentar sobre a camada de interface, onde tivemos as maiores mudanças e as maiores evoluções tecnológicas. Até lá!

, , , , ,

Deixe um comentário

ORM e Concorrência

Olá pessoal! Há algum tempo atrás sofremos um pouco na manutenção de um dos grandes sistemas da White Fox. Quero compartilhar a experiência para que outros não tenham que passar pelas mesmas dificuldades!

Só relembrando, conforme meu antigo post sobre a camada de domínio, nós utilizamos ORM (nHibernate ou Microsoft Entity Framework) para fazer o mapeamento de entidades para o banco de dados, de modo a abstrair os mecanismos de persistência. Isto tem funcionado muito bem para nós – utilizamos ORM em praticamente todos nossos sistemas de maior porte há mais de 10 anos com sucesso. Claro que por mais que tentemos compartimentalizar, ao longo do tempo os domínios acabam crescendo. Hoje temos sistemas com domínios de mais de 300 objetos e milhares de linhas de código de regras de negócio.

Pois bem, neste sistema em particular, começamos a perceber há algum tempo, problemas de dados somente no ambiente de produção. Valores que aparentemente ficavam errados do nada. E, obviamente, nenhum destes problemas aparecia em homologação ou durante a execução de testes unitários. Claro que, como todo acidente, nenhum erro mais grave tem uma única causa. Neste caso em específico, também temos uma sequência de eventos que levaram à falha. Olhando agora, são até que óbvios, mas gastamos um bom tempo no diagnóstico e solução.

O primeiro componente começou com uma prática usual de ORM. Quase todos possuem uma proteção para evitar que a persistência ocorra em um registro que foi alterado por outrem. Por exemplo, se o ORM carrega um objeto em memória, faz nele alguma alteração e ao salvar, detecta que o registro no banco não é o mesmo de quando o objeto foi carregado, ele gera uma exceção. Porém, este mecanismo deixa tudo lento, já que para implementá-lo, o ORM acaba tendo que fazer uma query a cada UPDATE. E como usamos transações, os LOCK do banco de dados acabam garantido a atomicidade da operação, assim esta proteção acaba ficando redundante. No nosso caso ela sempre é desligada.

O segundo componente é a criação de campos para contadores ou totalizadores. Sim, claro, isto é algo que se deve evitar, especialmente como atributo de uma entidade de domínio. Por exemplo, colocar o total de uma nota fiscal como atributo ao invés de calcular o total através da soma de seus elementos. Usualmente evitamos isto, mas, em algumas entidades, calcular o valor toda hora pode ser complicado, seja porque existe alguma regra de negócio muito complexa envolvida ou um número muito grande de elementos para compor o total. Então, em algum momento, alguém resolve que manter o totalizador oferece uma melhor razão custo/benefício. Obviamente que se usam transações para manter os totais e se criam testes unitários para garantir que os totalizadores funcionam em todos os cenários.

O último componente do problema é a concorrência. Dificilmente teste unitários são criados para simular uma carga de múltiplos usuários simultâneos, pela complexidade de se simular este tipo ambiente. Assim, no teste unitário, quase não há concorrência. Porém, em produção os nossos sistemas são usados por centenas de usuários simultâneos. Apesar dito, tipicamente um usuário faz transações em uma única grande “entidade” por vez (p. ex., vendendo um produto). Assim, mesmo vários usuários em paralelo dificilmente mexem na mesma entidade, simultaneamente, ao mesmo tempo.

Mas as exceções é que fazem a coisa desandar. Se desligamos a proteção de dados alterados, usarmos totalizadores em entidade e usuários alteram esta mesma entidade quase ao mesmo tempo, temos o nosso problema acontecendo! Para detalhar, vejam a figura a seguir. Imaginem 2 processos executando em paralelo, em tempos muito próximos um do outro.

image

Em um momento 1, ambos carregam a mesma entidade em memória, uma delas com um atributo totalizador. Como neste momento ainda não houve nenhuma transação, ambos conseguem carrega-la simultaneamente, e ambas possuem o mesmo valor para o atributo. No momento 2, ambos fazem algum processamento em que vão atualizar o campo totalizador. No momento 3, o primeiro processo abre uma transação, salva os objetos e faz o COMMIT. O segundo processo tenta fazer o mesmo, porém como o primeiro fez o LOCK, ele é bloqueado e fica em espera. Se não cair por timeout, quando o primeiro processo acabar, ele é liberado, começa sua transação, salva os seus objetos e faz o seu COMMIT. Como não há proteção para alteração, ele não vai perceber que o objeto foi alterado pelo primeiro processo e vai completar a ação achando que tudo correu bem.

Mas percebam que o segundo processo partiu objetos com totalizadores incorretos! Como o ele carregou os objetos no mesmo momento que o primeiro processo, ele não vai contemplar as alterações feitas por ele e vai salvar um total incorreto. Um exemplo, ambos carregam um atributo com um valor total de 10, ambos somam 1, o primeiro salva 11, o segundo também vai salvar 11, incorretamente!!!

Uma vez diagnosticado, o problema também não é simples de resolver. Não há solução trivial com o uso de ORM. Soluções como ativar a proteção de alteração, usar LOCK pessimista ou usar singletons, possuem pontos extremamente negativos e foram rejeitadas por nós. No final, a solução que adotamos foi, para campos totalizadores, ignorar totalmente o ORM e ir direto ao banco. Fizemos isto com o uso de um repositório especialmente projetado para este fim e com o uso de stored procedures para garantir que as alterações sejam feitas com as proteções adequadas. Esta solução conseguiu até mesmo melhorar o desempenho da aplicação, pois evitamos a manipulação de totais pelo ORM e transferimos todo o trabalho para o banco. O ponto negativo é que o sistema fica bem mais complexo de manter e uma porção das regras de negócio saiu do domínio e foi para o banco. Mas de todos os males possíveis, este foi o que achamos de menor impacto.

Moral da história é: conheça seu ORM, evite a todo custo campos totalizadores e, se tiver que usá-los, não se esqueça que poderá ter graves problemas em um ambiente com concorrência.

Este e outros eventos tem nos feito repensar o uso de ORM como um absoluto. Acho que já está na hora de refazer minha série sobre produtividade, atualizando-a com as tecnologias e práticas que temos adotado nos últimos anos. Tudo muda e TI muda ainda mais rápido. Acho que até que conseguimos ter uma relativa estabilidade nos nossos ambientes por muito tempo. Mas obviamente a evolução é necessária e tem hora que mudar paradigmas é essencial para mantermos nossa produtividade. Mais sobre isto em breve!

Até a próxima!

, , ,

1 comentário

Arquitetura de Microservices utilizando o Microsoft SQL Service Broker – Parte 3 – Final

Olá! Antes de iniciar a última parte desta série, uma notícia boa: a White Fox se tornou parceiro Microsoft Silver em Application Development e Gold em Devices and Deployment. Além do reconhecimento, a parceria com a Microsoft é importante para dar uma visibilidade maior para a White Fox e nos permitir acesso a muito mais recursos para nosso processo de desenvolvimento. Obrigado a todos que nos ajudaram durante a certificação! Em 2016 a White Fox deve focar mais no Microsoft Azure, sempre buscando trazer o melhor da tecnologia para nossos clientes com a melhor relação custo/benefício.

No último post, mostramos a estrutura montada no SQL Service Broker para suportar nossa arquitetura de Microservices. Nesta última parte vamos mostrar as estruturas em .NET que suportam o Thin Client (cliente) e os executores. Como estamos montando uma arquitetura expansível, criamos então uma classe estática, utilizando um container Microsoft Unity, que chamamos de ServiceBus, para registrar cada cliente e executor. O código dela pode ser visto abaixo.

public static class ServiceBus {
	private static IUnityContainer iocCcontainer;
	private static bool isInitialized;

	public static void InitializeSql(string connectionStringName) {
		isInitialized = true;
		iocCcontainer = new UnityContainer();
		iocCcontainer.RegisterInstance(typeof(IStorage), new SqlStorage(connectionStringName));
	}

	public static T GetService<T>() where T: class, IService {
		if (!isInitialized) throw new ServiceBusNotInitializedException();
		return iocCcontainer.Resolve<T>();
	}

	public static void RegisterService<T>(Type type) where T : class, IService {
		if (!isInitialized) throw new ServiceBusNotInitializedException();
		iocCcontainer.RegisterType(typeof(T), type);
	}

	public static void RegisterComponent<T>(T instance) where T : class, IComponent {
		if (!isInitialized) throw new ServiceBusNotInitializedException();
		iocCcontainer.RegisterInstance(typeof(T), instance);
	}
}

As interfaces IService são os serviços de mensageria, tanto do cliente quanto do executor. As interfaces de IComponent são para classes que são responsáveis por implementar as regras de negócio do executor. Abaixo um exemplo de uso do ServiceBus, onde o ResultMessagesReceiver (que implmenta IReceiver) é o responsável por processar as mensagens de retorno recebidas e o ClienteService (que implementa IClientService) é o Thin Client.

 ServiceBus.InitializeSql("ServiceBroker");
 ServiceBus.RegisterComponent<IReceiver>(new ResultMessagesReceiver());
 ServiceBus.RegisterService<IClientService>(typeof(ClientService));

Criamos também um ServiceBase, que é usado por executores e clientes para implementar as rotinas principais de processamento de mensagens. O código está abaixo. É praticamente um loop onde as mensagens são lidas do SQL Server, dentro de uma transação, e processadas. Em caso de qualquer problema, a transação pode ser desfeita e a mensagem permanece lá. É importante notar que caso aconteçam muitos rollbacks em sequência, o SQL Server desativa a Queue (isto nos causou alguns problemas de debug!). Neste caso, a Queue deve ser reativada antes que outras mensagens possam ser lidas.

protected void ProcessMessages(int maxMessages) {
	var counter = 1;
	do {
		if (++counter > maxMessages) break;
		storage.BeginTransaction();
		try {
			var message = storage.ReadMessage(endPoint);
			if (message == null) {
				storage.Commit();
				break;
			}
			if (string.IsNullOrEmpty(message.Handle)) {
				storage.Commit();
				break;
			}
			if (initiator && message.MessageType == EndMessageType) {
				storage.EndConversation(message.Handle);
				storage.Commit();                        
				continue;
			}
			if (message.MessageType == ErrorMessageType) {
				// todo: logar o erro
				storage.EndConversation(message.Handle);
				storage.Commit();                        
				continue;
			}
			message.Date = DateTime.Now;
			if (!ProcessMessage(message)) {
				storage.Rollback();
				break;
			}
			if (!initiator) storage.EndConversation(message.Handle);
			storage.Commit();                    
		}
		catch (Exception) {
			storage.Rollback();
			throw;
		}                               
	} while (true); 
}

Abaixo está a parte relevante do código que implementa o storage do SQL Server. Optamos por simplesmente encapsular chamadas a stored procedures que ficam no banco de dados utilizado para as filas de mensagens. A seguir estão também as 3 procedures de leitura, envio e final de conversação.

 public string SendMessage(EndPointConfiguration endPoint, string data, string conversationHandle = null) {
	var parameters = new Parameters()
		.Add("initiatorService", endPoint.Initiator).Add("targetService", endPoint.Target)
		.Add("contract", endPoint.Contract).Add("messageType", endPoint.MessageType)
		.Add("data", data);
			
	if (!string.IsNullOrEmpty(conversationHandle))
		parameters.Add("handle", conversationHandle);
	StoredProcedureFacility.ExecuteScalar<string>(connectionStringName, "SendMessage", parameters)	
}

public Message ReadMessage(EndPointConfiguration endPoint) {
	var root = StoredProcedureFacility.GetXml(connectionStringName, "ReadMessage", Parameter.Create("queueName", endPoint.QueueName));
	return new Deserializer<Message>(root)
		.Property(m => m.MessageType, "mt")
		.Property(m => m.Contents, "data")
		.Property(m => m.Date, "dt")
		.Property(m => m.Handle, "ch")
		.Instance();
}

public void EndConversation(string handle) {
	StoredProcedureFacility.ExecuteNoResults(connectionStringName, "EndConversation", Parameter.Create("handle", handle));
}

Stored procedures:

CREATE PROCEDURE [dbo].[SendMessage] (
	  @initiatorService sysname,
	  @targetService varchar(255),
	  @contract varchar(255),
	  @messageType varchar(255),
      @data varchar(MAX) = NULL,
	  @handle varchar(255) = null
)
AS 
BEGIN
	if @handle is null 
	begin   declare @id uniqueidentifier   BEGIN DIALOG CONVERSATION @id
            FROM SERVICE @initiatorService
            TO SERVICE @targetService
            ON CONTRACT @contract
            WITH ENCRYPTION = OFF, LIFETIME = 7200;
		set @handle = cast(@id as varchar(255));
	  end;
	  send on conversation @handle message type @messageType (@data)                  
      select @handle;	  
END
GO

CREATE procedure [dbo].[ReadMessage](@queueName varchar(255))
as 
begin
declare @ch varchar(255)
declare @mt varchar(255)
declare @data varchar(max);
declare @dt DateTime;

set nocount on

declare @Sql nvarchar(max) = N'RECEIVE TOP(1) @h = conversation_handle, @messageTypeName = message_type_name, @Packet = message_body, @date = message_enqueue_time FROM ' + @queueName + ';'

EXECUTE sp_executesql @Sql, N'@h UNIQUEIDENTIFIER OUTPUT, @messageTypeName varchar(255) OUTPUT, @Packet VARCHAR(max) OUTPUT, @date datetime OUTPUT'
		,@h = @ch OUTPUT
        ,@messageTypeName = @mt OUTPUT
        ,@Packet = @data OUTPUT
		,@date = @dt output;

select * from (select @ch ch, @mt mt, @data [data], @dt [dt]) M for xml auto

end
GO

CREATE procedure [dbo].[EndConversation](@handle varchar(255)) as
begin
;end conversation @handle;
end

Finalmente, a seguir está o exemplo de um Thin Client, com um envio de mensagem e a rotina que faz o processamento das mensagens de retorno. Para cada tipo de ação criada, deve haver um tratamento específico. O construtor recebe, por injection (o Unity cuida disto) o storage e a classe que irá tratar as regras de negócio.

public class ClientService: ServiceBase, IClientService {	 
    private readonly IReceiver receiver;

    public ClientService(IStorage storage, IReceiver receiver)
		: base(storage, true, Configuration.Requester.Service, Configuration.Executer.Service, Configuration.Contract, 
			Configuration.Requester.Message, Configuration.Requester.Queue) {
		this.receiver = receiver;
	} 
	
    public void RequestAction1() {
      SendMessage(new MessageContent { Action = Actions.Action1});
    }

    public override bool ProcessMessage(Message message) {
       var ser = new DataContractJsonSerializer(typeof (MessageContent));        MessageContent content;        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(message.Contents))) {
		content = (MessageContent) ser.ReadObject(ms);        }        if (content == null) return true;
	try {      switch (content.Action) {
		case Actions.Action1:     return receiver.ReceiveMessage(content, message.Date);
		default:     return receiver.Error(content.Action, ErrorCodes.NotImplemented, null);
		}
	}
	catch (Exception ex) {    return receiver.Error(content.Action, ErrorCodes.Exception, ex);
    }
}

O executor não é diferente, como pode ser visto abaixo. Ele somente recebe a mensagem, faz algum tipo de ação de negócio específica e retorna os dados para o solicitante. Claro que isto é um código simplificado, já que em um código de produção é importante tratar todos os tipos de erros possíveis, evitando rollbacks da fila.

public override bool ProcessMessage(Message message) {
	var ser = new DataContractJsonSerializer(typeof(MessageContent));
	MessageContent message;
	using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(message.Contents))) {
		message = (MessageContent)ser.ReadObject(ms);
	}
	if (message == null) return true;
	var returnMessage = new MessageContent {Action = message.Action};
	try {
		switch (message.Action) {
			case Action.Action1:
				returnMessage.Data = MyBusinessRulesManager.ExecuteSomething(message).ToString();
				break;
		}
	}
	catch (Exception ex) {
		returnMessage.Error = ex.Message;
	}
	SendMessage(returnMessage, message.Handle);
	return true;
}

Bom, espero ter conseguido passar uma visão geral da arquitetura de Microservices que estamos utilizando. Claro que temos muitos outros cenários que fazem com que a complexidade desta arquitetura seja bem maior. Por exemplo, temos situações onde temos mensagens geradas em horários específicos ou que só podem ser executadas em horas úteis. Isto faz com que o Executor tenha toda uma lógica para armazenar as mensagens com agendamento de execução específica em outras filas. Mas tudo isto é feito com o fundamento que mostrei nesta série. Como sempre, fiquem à vontade para entrar em contato para tirar dúvidas ou conversar mais sobre este assunto. Até a próxima!

, , , , ,

Deixe um comentário

Novidades!

Olá pessoal, há quanto tempo!! :)…. Estive ontem no Visual Studio Summit 2015 e continuo achando este o melhor evento da Microsoft para desenvolvedores. Este ano esteve especialmente bom já que temos muitas tecnologias e plataformas a caminho, tais como o Visual Studio 2015, o ASP.NET 5 e o C# 6.0. Foi bom poder conversar como os early-adopters e colher opiniões sobre o que temos de bom em cada novidade destas. Sem entrar muito em detalhes, é suficiente dizer que fiquei positivamente impressionado, fazia tempo que não via a Microsoft engajada em disponibilizar tantos recursos para os desenvolvedores. E é perceptível também o esforço em começar a integrar os produtos Microsoft com as outras plataformas, como o Visual Studio Code para Mac e Linux e nos novos recursos de integração do Visual Studio 2015 RC com o melhor do mundo open source, tais como Node.js NPM, Grunt e Bower. Para quem ainda não baixou o Visual Studio 2015 RC, recomendo fazer isto logo, vale a pena no mínimo para se preparar para estas mudanças.

Na White Fox, continuamos firmes na nossa transição de desenvolvimento MVC clássico para um modelo baseado em MVC REST APIs e uma interface rica baseada em AngularJS. Praticamente todos os nossos sistemas já possuem telas desenvolvidas com esta tecnologia e novos sistemas estão sendo feitos exclusivamente neste formato. É cada vez mais comum também expormos estas APIs para interfaces desenvolvidas para dispositivos móveis, o que faz com que nossas APIs tenham que ser desenvolvidas com um agnosticismo completo com relação às interfaces que a vão utilizar. Se por um lado isto é uma boa prática, por outro significa migrar centenas de telas de legado para este novo formato, o que muitas vezes é bem custoso.

Na parte de ORM, continuamos sem um norte claro. A maior parte de nossos sistemas continua usando a versão antiga do nHibernate com o Castle ActiveRecord. Porém a idade está começando a pesar nesta plataforma, começando a tornar difícil algumas evoluções que precisamos fazer. Fizemos algumas tentativas de migração para o Entity Framework com relativo sucesso usando o Entity Framework 6. Porém, como nossos sistemas são muito complexos (centenas de entidades), o esforço de mapeamento é considerável, e temos bugs sérios no designer do EF6 com relação a defaults de banco de dados, o que torna a atividade ainda mais complicada. E, para detonar de vez este cenário, o EF7 vai remover o modelo “database-first”, o que inviabiliza o que fizemos até agora.

Esta questão do EF7 me intriga. Conversando ontem com outros desenvolvedores, percebi que muitos têm problemas similares. O uso do EF com sucesso acontece quando o sistema é novo e tem um único “dono” para banco de dados. Porém isto não é comum na nossa realidade (e, pelo que vi, de muitas outras), pois o banco é compartilhado entre vários sistemas legados e não há um único responsável pela evolução do mesmo. Neste aspecto, até a proposta da Microsoft para substituir o modelo database-first pelo code-first com migrations não resolve, pois temos alterações que não viriam do EF. Parece que não teremos alternativa a não ser codificar os objetos na mão (e voltamos 10 anos no tempo!!). Pelo menos o uso do Power Tools para o EF deve ajudar um pouco. E estamos de olho em ferramentas de terceiros, como as da DEVART, que também podem ser uma alternativa. Mantenho vocês informados da nossa evolução.

Outro tópico muito interessante ontem foi o relativa à Microservices. Percebi que outras pessoas estão com o mesmo problema que o nosso, que é manter a agilidade em sistemas de grande porte, onde o processo de deploy é amarrado por inúmeros problemas: demoras em homologação, sistema monolítico etc. Microservices podem ser uma solução para isto, a ideia é decompor o sistema em componentes de serviço e com isto podermos substituir um ou outro sem afetar a aplicação como um todo. Em teoria, a ideia é boa, mas, na prática, ainda não temos uma plataforma definida para implementarmos isto. As implementações de Microservices normalmente fazem uso de filas de mensagens, o que gera problemas para sistemas que exigem uma resposta síncrona. Além disto ainda temos que escolher uma boa plataforma para implementar este barramento de serviços/mensageria. O Microsoft Azure tem uma implementação interessante, porém ele não é uma opção para sistemas internos. Estamos analisando possibilidades como o Service Broker do próprio SQL Server ou soluções open source como o RabbitMQ, vamos ver como fica isto nos próximos meses.

Até a próxima!

, , , ,

2 Comentários

Imagina na Copa!

Olá pessoal! Acharam que eu tinha abandonado este blog em definitivo?! Negativo, ainda estou firm e forte por aqui! :-)… Só que vou parar de prometer voltar a escrever em um ritmo normal, já que cada vez que faço isto, acontece algo que me impede totalmente… No último post, falei sobre o aplicativo do Sistema Poliedro que estávamos trabalhando e que posteriormente foi denominado P4Ed (ou simplesmente P+). Então, de lá para cá ficamos (e continuamos!) completamente envolvidos com ele, muitas novas funcionalidades, várias versões, centenas de APIs… A compensação é que vemos o resultado, utilizado por milhares de alunos todos os dias, e nos sentimos orgulhosos de termos contribuído. Os aplicativos são um sucesso e nem tudo ainda foi disponibilizado, as próximas versões irão conter ainda mais funcionalidades e características e farão o P+ ser cada vez mais uma referência absoluta de mercado.

O trabalho neste projeto nos fez confrontar vários “dogmas” internos da White Fox. Como visto em posts anteriores, um dos grandes diferenciais da White Fox é o uso de um framework que permite grande produtividade no desenvolvimento de sistemas e, em especial, de interfaces. Porém, nestes 4 anos de empresa, duas coisas aconteceram: como mencionamos no post anterior, o trabalho do P+ nos fez focar na entrega de produtos SEM interface; nós fomos encarregados de desenvolver APIs e regras de negócio de servidor enquanto que empresas parceiras trabalham em paralelo na confecção de interface. Embora isto nos permitiu desenvolver o P+ em tempo recorde, ele fez com que grande parte do nosso framework fosse totalmente descartada. Daí tivemos que nos reinventar pra conseguir, tendo uma API como produto final, ter a mesma produtividade que estávamos acostumados.

O segundo fato importante destes 4 anos foi uma mudança significativa na característica das interfaces. A web continua importante, mas temos também agora, em pé de igualdade, interfaces de dispositivos móveis (em seus vários tipos) e integrações diversas com outros sistemas e plataformas. As próprias interfaces web, graças a uma evolução cada vez maior de frameworks javacript (como AngularJS, Backbone, Knockout etc.) fizeram com que todo o conceito de desenvolvimento mudasse. Com isto, parte de nosso framework perdeu sua aplicabilidade. Sobre isto, espero fazer um ou mais posts específicos, revisitando o assunto de produtividade.

O bom de se trabalhar com desenvolvimento é que o trabalho nunca é monótono. As mudanças acontecem, e em ritmo rápido. A própria Microsoft, talvez pressionada por plataformas diferentes, tem acelerado bastante o ciclo de vida de suas ferramentas e plataformas. Mal o Visual Studio 2013 foi lançado, no final do ano passado, e já tivemos o Update 1 (e, brevemente, o Update 2). Web API, aplicações MVC e a evolução acelerada da plataforma Azure, com um sem número de facilitadores, módulos e serviços prontos, faz com que tenhamos que repensar toda nossa infraestrutura de código. Só que temos que fazer isto com o avião voando; temos um sem número de sistemas pra manter, que devem continuar funcionando e ao mesmo tempo serem evoluídos para fazer uso de toda esta nova tecnologia.

Junte a isto tudo o desafio de escalabilidade do P+, também mencionado anteriormente. Embora, como arquitetos, nós busquemos fazer sistemas que sejam escaláveis, isto vale até certo ponto. Uma coisa é projetar um sistema para 10 usuários que pode chegar a 10.000 em um ano. Outra, totalmente diferente, é projetar um sistema para 5.000 que pode chegar a 1.000.000 de usuários em pouco tempo. Isto exige um grande planejamento, um trabalho grande de identificação de gargalos e, às vezes, soluções pouco ortodoxas. No P+ temos trabalhado incessantemente para buscar arquiteturas que permitam suportar grandes volumes na Cloud sem ter que reescrever a aplicação a cada aumento. É grande desafio e que, quando não adequadamente tratado, gera situações complicadas de gerenciar. Mais ou menos como suportar uma Copa do Mundo em um ambiente sem a infraestrutura adequada. E nem é só uma questão de recursos, se eles forem mal empregados ou seu uso for mal planejado, vocês podem imaginar o resultado – ou vivê-los, como vamos ter a oportunidade de fazer aqui na cidade maravilhosa, em menos de 1 mês.

Vou ficando por aqui. Tenho vários tópicos mais técnicos rascunhados que espero em algum momento transformar em posts. Até a próxima então!

, , , , , , , , , ,

1 comentário

De volta 2!

Oi pessoal!! Depois de um looooongo tempo sem postar, finalmente consegui um tempinho. Olhando o último post, dá pra notar que estávamos extremamente ocupados no início do ano passado. Mal podíamos imaginar o que viria pela frente. Do ano passado para cá, iniciamos um enorme projeto com um de nossos clientes, o Sistema de Ensino Poliedro, que nos tomou praticamente 1 ano de contínua dedicação e deve ainda prosseguir por bastante tempo.

O projeto foi participar da construção de aplicativos móveis, iOS (iPhone e iPad) e Android (smartphones e tablets), para disponibilizar para alunos uma grande parte das informações até então só existentes no site, como agenda, notas, resultados de simulados etc. Nossa parte foi expor em serviços toda a infraestrutura do domínio que tínhamos em sistemas, de forma a funcionar tanto para os aplicativos móveis quanto para uma nova versão web, também a ser desenvolvida. Trabalhamos com empresas parceiras, que ficaram responsáveis pela construção das interfaces móveis e web.

O maior desafio foi a definição desta API de serviços, para ser o mais reusável possível sem comprometer a facilidade e uso pelas interfaces. E tinha que ser extremamente escalável, já que o número de alunos previstos para os aplicativos era superior a 30.000.

A nossa opção foi utilizar a ASP NET MVC Web API. Uma das grandes vantagens desta arquitetura foi que o Web API já é capaz de retornar dados em vários formatos, como XML e JSon, o que foi providencial para nossa meta de reusabilidade. E por ser bem leve, atendeu bem também a necessidade de escalabilidade.

Como o sistema é, em quase todas as telas, somente leitura, optamos por otimizar o acesso a dados com a criação de banco de dados construídos especialmente para este fim. Assim, desenvolvemos procedimentos para trazer dados dos nossos bancos transacionais e produzir uma informação desnormalizada, pronta para consumo pela interfaces. Embora isto tenha exigido uma grande capacidade de processamento para gerar esta informação consolidada (optamos por executar isto durante as madrugadas), o acesso de cada uma das interfaces ficou extremamente rápido e escalável.

O resultado foi um sucesso completo. Os aplicativos iOS e Android tiveram centenas de downloads nos primeiros dias e hoje, menos de 6 meses após o lançamento, já temos mais de 10.000 aparelhos, entre tablets e smartphones, com acessos de alunos. O resultado foi tão positivo que já continuamos no mesmo ritmo para construção de mais aplicativos, agora voltados para professores e gestores. Nosso cliente provavelmente é hoje uma das instituições mais modernas do país, em termos de software. Ficamos super orgulhosos de termos participado deste projeto!

Em paralelo a isto, outros clientes nossos estão nos demandando bastante. Com isto, a White Fox está totalmente ocupada e estamos em busca urgente por bons desenvolvedores. Se você quer saber mais ou se candidatar, envie-nos seu currículo para rh@whitefox.com.br

Espero não demorar mais um ano para o próximo post! Até breve!

P.S.: Para quem ainda não se inscreveu e está no rio, não percam o DNAD 2013, nestes dias 2 e 3 de agosto. A grade de palestras está excelente e a White Fox é uma das patrocinadoras!

, , , , , , , , ,

Deixe um comentário

De volta!

Oi pessoal! Longo tempo sem postar! Nos últimos meses acabamos ficando extremamente ocupados na White Fox, graças à coincidência da finalização de um grande projeto de um dos nossos maiores clientes com a finalização e o lançamento de mais um produto com a marca White Fox. Felizmente tudo correu excepcionalmente bem, apesar de ter dado muito trabalho e ainda estarmos trabalhando em um ritmo acima do normal. E, felizmente ou infelizmente (depende do ponto de vista), tudo indica que o ritmo este não vai diminuir em curto prazo, já que estamos com algumas iniciativas em andamento que, se concretizadas, irão significar mais um grande esforço adicional.

O projeto cuja primeira entrega foi no final do ano passado, representa o resultado de quase um ano de trabalho. Nele, tivemos que desenvolver um novo mecanismo de comunicação com o Banco Central do Brasil (BACEN), baseado em troca de mensagens e em substituição a um antigo formato baseado em emulação de terminais (sim, isto ainda existe!). O projeto foi de âmbito nacional e envolveu todos os bancos e corretoras de câmbio do país. A segunda fase do projeto está em andamento neste primeiro semestre e deve consumir esforço até meados deste ano. Por ser uma área de infraestrutura (mensageria), fizemos praticamente tudo utilizando o Web Service Factory, da Microsoft, que facilitou enormemente a criação de mensagens e serviços. As mensagens do BACEN são artefatos XML, o que nos fez utilizar XSLT para a geração e transformação das mesmas. As nossas escolhas permitiram que tivéssemos um desenvolvimento rápido e uma grande facilidade de manutenção, o que foi crucial para finalizarmos tudo a tempo. Estamos mantendo estas escolhas para esta segunda fase e, graças a elas, estamos adiantados no nosso cronograma.

O segundo grande evento foi a finalização da plataforma da XChange. Este produto é um site de comércio eletrônico de Internet, destinado à venda de cartões pré-pagos VISA Travel Money (VTM) para quem viaja ao exterior a turismo ou trabalho, no qual tenho participação. A White Fox foi chamada a fazer um software que funcionasse com o site estático desenvolvido por uma agência de propaganda. O desafio foi criar mecanismos que permitissem trabalhar as áreas dinâmicas do site sem prejuízo para o layout e para as funcionalidades de navegação. Para atender a este objetivo, tivemos que fazer uma evolução significativa no nosso framework de interface. O resultado foi melhor do que esperávamos, e agora o nosso framework suporta a integração em qualquer layout HTML, com um mínimo de esforço.

O princípio que utilizamos foi uma evolução do funcionamento anterior da nossa engine MVC, que era gerar HTML através de transformações XSLT. Nesta nova versão, ao invés de gerar a página inteira, nós somente geramos trechos da página. Assim, podemos apontar um determinado <DIV> em um HTML e ele é substituído, em tempo de renderização, por um <DIV> de conteúdo dinâmico. Isto facilita a construção e a visualização de páginas, permitindo a “injeção” de trechos dinâmicos nos arquivos HTML provenientes das agências, praticamente sem modificações nos mesmos (nem mesmo os famigerados <% %> das engines tradicionais MVC). O resultado foi excepcional e graças a esta nova versão, somos capazes de gerar sistemas de maneira ainda mais rápida.

Espero voltar a escrever posts com uma regularidade mínima. No próximo, vou falar um pouco sobre os desafios que enfrentamos recentemente com desenvolvimento para alto desempenho, com paralelismo em SQL Server. Até a próxima!

, , , , , ,

1 comentário