Arquivo para categoria White Fox

Banco NoSql em Azure

Olá pessoal, continuando a série iniciada aqui, esta semana vou detalhar como migramos nossa API de controle de acesso de um banco de dados relacional Sql Server para um banco de dados NoSql, em Azure.

Os bancos NoSql estão sendo cada vez mais empregados em aplicações web devido à sua escalabilidade e menor custo. No entanto, seu uso exige uma série de cuidados e nem todos os domínios podem ser facilmente mapeados para utilizar este tipo de estrutura de dados. Domínios que tenham consultas complexas, que dependam de índices ou de extensas manipulações de entidades ou que utilizem um número elevado de transações em entidades diversas, podem ter uma alta dificuldade de mapeamento.

No nosso caso, escolhemos migrar a nossa API de segurança e autenticação. Ela foi uma boa opção por ser baseada em domínio pequeno, com ações pontuais (login, cadastro) e poucas consultas avançadas. Outro ponto favorável é que nosso sistema de segurança é multi-tenant, o que é relativamente fácil de implementar em um banco NoSql mas no mínimo problemático, em um banco relacional. Mais detalhes sobre isto abaixo.

Para a migração para NoSql no Azure, avaliamos utilizar o CosmosDb e o Azure TableStorage padrão. O interessante é que a forma de acesso e a estruturação de dados são praticamente iguais nos dois, o que muda é que no CosmosDb temos alguns recursos a mais (como por exemplo a remoção automática de registros por tempo), além de um melhor suporte para escalabilidade. Porém, enquanto o Azure TableStorage é bem barato (praticamente cobrança baseada em espaço alocado), o CosmosDb já é bem mais caro – cobrado por operações realizadas. No nosso cenário, o custo mensal do CosmosDb chegou perto do custo do Sql Server – cerca de USD 60 por mês, para o tamanho e uso do nosso banco. Como não temos grandes necessidades de contingência, acabamos escolhendo o TableStorage, e o seu custo mensal ficou inferior a 10 USD.

A ideia do uso de um banco NoSql usando o TableStorage é relativamente simples: um conjunto de tabelas identificadas por 2 tipos de índices, PartitionKey e RowKey. Alguns bancos suportam mais tipos de índices, mas no caso do Azure TableStorage, só temos estes 2 mesmo. Por ser uma aplicação muti-tenant, o identificador do Tenant é o PartitionKey, o que nos deixa somente a RowKey para identificar as entidades. Se partirmos de um mapeamento clássico relacional, o RowKey seria a Primary Key (PK) que identificaria cada instância. As Foreign Keys (FK) podem ser mapeadas por propriedades (colunas) na tabela, resolvendo os mapeamentos do tipo 1:N. O problema são chaves compostas e relacionamentos N:N, que exigem um esforço maior de mapeamento. A ausência de outros índices explica por que é complicado mapear um sistema com grande quantidade de relatórios e consultas, já que teríamos que criar uma estrutura para representar cada índice complexo e armazenar neles as RowKeys relativas às entidades que são identificadas pelo índice. Possível, mas complexo.

No nosso caso, simplificamos o modelo para poucas entidades (ver diagramas abaixo, do modelo original e do NoSql), cada um identificado pela sua RowKey. Tínhamos algumas necessidades de relacionamentos N:N, como por exemplo a de aplicações e de permissões em usuários. Para estes casos, criamos uma coluna na tabela do usuário contendo a serialização da coleção destas entidades. Estas colunas são carregadas e deserializadas quando a entidade é trazida da TableStorage.

Modelo Relacional
Tabelas Azure Table Storage
Dashboard Azure Storage

Para fazer a manutenção das tabelas no Azure TableStorage, sugiro usar o Azure Storage Explorer. É uma aplicação bem simples mas que permite efetuar todas as operações necessárias.

Outro ponto que é bem diferente do modelo relacional é carga (load) das entidades. No modelo relacional, o típico é mapear todas as colunas para o objeto e carrega-lo de uma vez do banco de dados. Como no NoSql podemos ter muitas colunas que são utilizadas como repositório (como as de coleções, por exemplo), carregar todas as colunas seria um custo elevado. Assim, tipicamente, a cada operação de acesso ou de salvamento, somente as colunas afetadas são solicitadas ou alteradas.

Para facilitar a persistência das entidades, criamos um pequeno framework com as operações básicas. Denominamos “entidade raiz” aquelas que estão ligadas diretamente ao Tenant, tais como Application, User ou RefreshToken (ver modelo acima). As entidades raiz são carregadas diretamente pelo RowKey e pelo TenantId específico – ver trecho de código abaixo. As demais entidades são subordinadas a uma entidade raiz (como Roles ou Permissions) nelas, a PartitionKey é sempre a chave da entidade Raiz e a RowKey é a sua chave específica – ver abaixo. Fizemos também métodos para carregar ou salvar objetos na TableStorage que permitem especificar as colunas necessárias para cada operação. Finalmente, fizemos também métodos para facilitar a serialização/deserialização de coleções quando são repositórios em colunas.

public Task<T> LoadTenantRootEntity<T>(Guid id, params string[] columns) 
	where T : TableStorageEntity, new() {
	return LoadTenantRootEntity<T>(GuidToString(id), columns);
}

public async Task<T> LoadTenantRootEntity<T>(string id, params string[] columns) 
	where T: TableStorageEntity, new() {
	var retrieveOperation = TableOperation.Retrieve<T>(tenant.Identifier, id, columns.Length > 0 
		? columns.ToList() 
		: null);
	var result = await Table<T>().ExecuteAsync(retrieveOperation);
	return result.Result as T;
}

Código para carga de entidade raiz com TenantId

protected async Task<IList<T>> LoadAll<T>(string partitionKey, params string[] columns) where T : TableStorageEntity, new() {
	var query = new TableQuery<T>().Select(columns).Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));
	TableContinuationToken continuationToken = null;
	var results = new List<T>();
	do {
		var result = await Table<T>().ExecuteQuerySegmentedAsync(query, continuationToken);
		continuationToken = result.ContinuationToken;
		results.AddRange(result.Results);

	} while (continuationToken != null);
	return results;
}

Código para carga de entidades filtradas por Partition Key

O resultado ficou bem interessante. As operações básicas ficaram bastante simples e o tempo de resposta das operações ficou muito bom. Em vários cenários, temos um desempenho bem superior ao modelo relacional – nas operações de login, por exemplo, tivemos um ganho muito expressivo. A implementação de multi-tenant ficou muito mais simples do que seria em um modelo relacional, graças aos índices de PartitionKey.

A conclusão é que o banco NoSql é uma boa alternativa pra vários cenários, mas nem sempre é algo simples de se modelar. É um mecanismo de persistência muito mais barato, em termos de Azure, quando comparado a um banco Sql Server. E, dependendo da implementação, é mais rápido e mais facilmente escalável do que um Sql Server tradicional. Como tudo em TI, é conveniente pesar os prós e contras antes de partir para uma linha NoSql. E sempre fazer uma prova de conceito antes de iniciar a migração. Mas tenho certeza que, se bem construído, um sistema com banco NoSql pode ser uma melhor opção do que a linha relacional padrão, em vários cenários.

Fiquem à vontade para comentar ou se tiverem alguns outros cenários de aplicações NoSql e quiserem trocar uma ideia. O próximo post será sobre o Azure Api Management. Até lá!

, , ,

Deixe um comentário

Azure DevOps

Oi pessoal! Iniciando a sequência de artigos sobre tecnologias e plataformas citadas aqui, vou começar pelo básico: configuração de ambiente DevOps no Azure.

Embora DevOps esteja em adoção crescente há vários anos, sua adoção em larga escala só se tornou possível com o foco da Microsoft no Azure DevOps. A migração do antigo TFS para o GIT, a integração de todas as ferramentas em https://dev.azure.com/[suaempresa] e  a automatização via pipelines de CI (Continuous Integration) e CD (Contiunous Deploy) tornou todo o processo simples de ser implementado.

Bom, vamos assumir que estamos começando do zero e montar um ambiente de DevOps no Azure e mostrar todos os passos necessários. O único requisito é ter uma assinatura Azure válida para se registrar os artefatos.

O primeiro passo é criar a empresa no DevOps. Isto pode ser feito em https://dev.azure.com, bastando seguir os tutoriais. O primeiro artefato que temos que criar é o projeto inicial. O termo projeto aqui pode gerar confusão, pois há a tendência de se tratar um projeto como projeto Visual Studio. Mas, na nossa experiência, é mais produtivo tratar um projeto DevOps como sendo todo o contexto de um cliente. Assim, todas as solutions, projetos visual studio, artefatos etc. relativos a um cliente normalmente são armazenados em um único projeto DevOps. Abaixo a tela de gestão do projeto no Azure.

Dentro de um projeto podemos ter múltiplos repositórios GIT. Usualmente é criado um repositório para cada projeto do tipo bibliotecas, serviços ou sistemas. Já vi algumas defesas de se ter um repositório único para tudo (nós até já chegamos a usar este formato por algum tempo), mas isto complica a geração dos pipelines de CI. Assim, preferimos usar repositórios separados mesmo. Como é típico em um repositório GIT, temos o branch master, que armazena a versão de produção e branchs de desenvolvimento, usados para gerar as versões para testes e homologação. Em organizações que precisam de um compliance mais forte, pode ser criada uma política que impeça o commit direto no branch master, exigindo um pull request do desenvolvimento para ele. Este pull request pode também exigir aprovadores, o que garante um alto controle do que segue para o master. Claro que isto tem um impacto negativo em produtividade. Como na White Fox o objetivo é agilidade, nós não temos estes controles ativados.

Outro ponto importante para configurar no projeto é um feed de artefatos (último elemento na barra lateral esquerda). Este feed tem como finalidade a publicação dos pacotes Nuget das bibliotecas utilizadas naquele projeto DevOps. Embora o feed seja para uso interno (ele exige autenticação), dá para se configurar acesso para outros projetos ou mesmo para outras empresas. Na White Fox, estamos migrando todos os nossos pacotes Nugets de um servidor público para um feed de projeto, simplificando nossa infra e facilitando o processo de CI/CD.

Com o feed de artefatos criado, o próximo passo é criar os pipelines de CI para cada projeto utilizado. Ao se criar um pipeline, temos duas opções: utilizar pipelines clássicos ou baseados em arquivos yaml. Em geral, o pipeline clássico atende os cenários típicos, enquanto que os baseados em arquivos yaml permitem uma maior flexibilidade e customização das tarefas. A figura abaixo mostra a tela de criação. Inicialmente se escolhe o repositório de origem e aí basta seguir o assistente que vai gerar o arquivo yaml. Para escolher criar um pipeline clássico, basta usar o link no final da tela.

Usualmente nossos pipelines de CI fazem as seguintes tarefas: 1 – baixar artefatos nuget; 2 – compilar projetos; 3 – executar testes unitários; 4 – publicar artefato no próprio pipeline. Abaixo um pipeline clássico web e um de arquivo yaml.

Um alerta: apesar da Microsoft já ter recentemente permitido o desenvolvimento de Azure Functions em .NET Core, ainda não é possível fazer um pipeline para deploy deste tipo de Function. Se alguém tiver este cenário, é necessário por enquanto ainda fazer uma publicação manual – o que não é nada complexo, dá pra fazer direto do Visual Studio. Creio que em breve este tipo de pipeline também estará disponível.

Após termos os pipelines de CI funcionando, o último passo é criar os pipelines de release – CD. Usualmente o pipeline de release é criado baseado em um trigger de pipeline de CI bem sucedido. O pipeline de CD simplesmente baixa os artefatos e, caso seja uma aplicação web, publica direto no Web App; caso seja um Nuget, publica direto no feed de artefatos. Abaixo exemplos de pipelines Nuget e web app. Abaixo um pipeline web.

Finalizando os pipelines de release, voi-la, temos todo o nosso ambiente operacional em DevOps CI/CD. Um commit no master irá iniciar o pipeline de CI, que irá compilar, testar e deixar pronto pra publicação. Quando o CI finalizar, o pipeline de CD irá pegar os artefatos e automaticamente publicar no servidor Nuget ou direto no Web App.

Um último item que gostaria de apontar é sobre notificações. O DevOps por padrão já envia e-mails no sucesso ou falha do CI para quem fez o commit. É tranquilo também configurar o envio de e-mails para um grupo no caso de sucesso ou falha – dá até pra criar automaticamente workitems em falhas, se for desejado. No nosso caso, como somos usuários do Microsoft Teams, nós preferimos receber as notificações por ele.

Existe um bot público chamado Azure Pipelines – basta buscar em aplicativos no Teams. Ao instalar, ele permita que façamos assinaturas dos pipelines e publiquemos notificações em canais específicos. Assim, toda vez que um Release é feito com sucesso, todos nós da White Fox recebemos uma notificação no Teams automaticamente.

É isto pessoal. Uma vez configurado, o Azure DevOps funciona impressionantemente bem. Todos os nossos artefatos principais já estão em CI/CD e isto tem poupado muito tempo nosso em testes e deploys. Recomendo a qualquer empresa de software que passe a usar o Azure DevOps com CI/CD, se ainda não o faz!

No próximo artigo vou falar um pouco de services .net core usando NoSql. Até breve!

, , , , , , , ,

Deixe um comentário

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

White Fox: Ano I

Oi pessoal! Este mês comemoramos um ano de White Fox… Nem parece que já se passou um ano desde o início! Acho que o tempo passa mais depressa quando estamos fazendo algo que gostamos. Depois de definirmos o conceito do que buscávamos e iniciar a empresa, passamos este ano consolidando e definindo processos de trabalho e refinando nossos objetivos e áreas de atuação.

Na parte interna, conseguimos evoluir muito mais do que esperava. Nosso framework está extremamente maduro, conseguimos atingir o que eu sempre buscava em termos de produtividade, para manutenção e sistemas novos. Claro que ainda tem muita coisa a evoluir, especialmente nas partes mais complexas, ligadas à interface. Mas nas áreas de infra-estrutura, de suporte ao domínio (repositórios, serviços) e de serviços horizontais (segurança, log, agendamento), atingimos um nível ótimo. Atualmente, quando iniciamos uma nova aplicação, perdemos muito pouco tempo até termos algo básico no ar, com todas as funcionalidades mínimas necessárias (login etc.). Vamos continuar evoluindo, mas ele está tão maduro que hoje precisamos gastar muito pouco tempo na sua manutenção e evolução.

Outra área que evoluiu muito mais do que esperava foi a parte de gestão de processos de desenvolvimento. Graças ao esforço do Christian (que gosta muito desta área), conseguimos definir, implantar e criar ferramentas de gestão dos processos de desenvolvimentos ágeis que usamos nos nossos projetos novos e de manutenção. Ainda tem muita coisa a ser feita, queremos automatizar mais, termos uma ferramenta mais robusta (e talvez até um produto derivado dela) e dar ainda mais transparência para nossos clientes. Estamos criando algo muito interessante para a gestão de projetos de manutenção no formato ágil. No decorrer deste segundo ano, com certeza teremos novidades neste aspecto.

Refinamos um pouco os objetivos da empresa. Continuamos a buscar uma excelência técnica e um local de trabalho extremamente agradável para o desenvolvedor. E preferimos manter uma equipe pequena, mantendo nossa maneira de trabalho, do que crescer e sacrificar qualidade. Hoje o software White Fox é utilizado principalmente quando o cliente quer algum diferencial de qualidade e velocidade de construção. Continuamos a selecionar também nossos projetos, preferindo clientes que possuam experiência na contratação de desenvolvimento ágil e que consiga identificar um ROI nos projetos que necessita.

Consolidamos também nosso programa de novos integrantes. Hoje a White Fox possui somente 3 posições: estagiários, sócios juniores e sócios sêniores. O programa de estágio é utilizado para identificar e treinar novos talentos. Sócios júniores são pessoas que possuem um talento excepcional, estão identificados com os nossos valores e estão em um momento de transição até ter todo o necessário para ser um sócio sênior. E os sócios seniores participam da gestão da empresa e são convidados a atuar como investidores em todos os nossos empreendimentos. O modelo enxuto e claro facilita a conversa com novos interessados e auxilia na identificação do que é necessário para se fazer para ser parte dela.

As perspectivas para o segundo ano são ótimas. Continuaremos a atuar nas melhorias do nosso processo interno de desenvolvimento, buscando cada vez mais inserir o cliente com o uso de ferramentas automatizadas. Continuaremos a buscar projetos onde o software seja o diferencial competitivo. E continuaremos a trabalhar pra fazer produtos cada vez melhores, de maneira rápida e com um custo adequado. Vou mantê-los informados de como isto está evoluindo aqui por este blog. Até a próxima!

, ,

2 Comentários