sexta-feira, 28 de dezembro de 2018

Evolutionary design

https://www.industriallogic.com/blog/evolutionary-design/
https://www.industriallogic.com/blog/evolution-cupcakes-and-skeletons/
http://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp
http://www.jpattonassociates.com/dont_know_what_i_want/

quarta-feira, 8 de agosto de 2018

SignalR c/ SimpleInjector DependencyResolver

SignalR é sem dúvida uma ótima biblioteca, mas eu me deparei com muitas dificuldades ao tentar integrar o SimpleInjector com a versão 2.3.0 (última versão até o momento da liberação deste post). Pretendo dizer o porquê e como resolvi.

No MSDN é possível encontrar a documentação oficial para implementação de um custom Dependency Resolver (https://docs.microsoft.com/en-us/aspnet/signalr/overview/advanced/dependency-injection), porém eu acredito que o código que está lá não funciona. Segue o código proposto pela Microsoft:

internal class NinjectSignalRDependencyResolver : DefaultDependencyResolver
{
    private readonly IKernel _kernel;
    public NinjectSignalRDependencyResolver(IKernel kernel)
    {
        _kernel = kernel;
    }

    public override object GetService(Type serviceType)
    {
        return _kernel.TryGet(serviceType) ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType));
    }
}

Baseado neste código, o passo inicial foi tentar criar um parecido, só que usando o SimpleInjector.

internal class SimpleInjectorAsSignalrDependencyResolver : DefaultDependencyResolver
{
    private readonly Container_container;
    public SimpleInjectorAsSignalrDependencyResolver(Container container)
    {
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        return _container.GetInstance(serviceType) ?? base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return _container.GetAllInstances(serviceType).Concat(base.GetServices(serviceType));
    }
}

Mas...
  1. Nesta classe foi implementado apenas os métodos GetService. Analisando o código do DefaultDependencyResovler (https://github.com/SignalR/SignalR/blob/2.3.0/src/Microsoft.AspNet.SignalR.Core/DefaultDependencyResolver.cs) é possível perceber que default services são registrados através do método Register, e estes caras não são salvos no container. Ou seja, se você ou o próprio SignalR precisar requisitar os default services, esquece, pois o SimpleInjector gera exceptions quando os serviços não estão registrados! Foi o meu caso ao tentar implementar um custom HubDispatcher.
    Uma possível solução seria colocar um try-catch mitigando quaisquer ActivationExceptions para sempre retornar nulo, mas isso abafa erros legítimos (ou seja, erros que não são frutos da indisponibilidade dos serviços no container).
    Uma outra possível solução seria eu fazer o meu sistema registrar os default services manualmente, mas isso aumentaria fortemente o acoplamento entre o custom resolver a versão do DefaultDepedencyResolver, além de quebrar o Open-Closed Principle. Sendo assim, precisei também implementar os métodos de Register para que os default services fossem devidamente registrados.
  2. Uma implementação simplória dos métodos Register não funciona também, pois o construtor da classe base começa um processo de registro de serviços e, antes mesmo que o construtor do custom resolver fosse chamado e o container fosse salvo localmente, os métodos de Register são invocados, gerando NullReferenceException. Que mancada, hein Microsoft!
  3. Além do problema acima, um simples esquema para postegar o registro dos serviços para um momento após o container ficar disponível também não funciona, pois no próprio processo de registro os métodos GetServices são invocados, requerindo a disponibilidade do container. Que mancada, hein Microsoft! 
Depois desses problemas, cheguei à seguinte implementação:

public class SimpleInjectorContainerAsSignalRDependencyResolver
 : DefaultDependencyResolver
{
 #region ' ctor '

 public SimpleInjectorContainerAsSignalRDependencyResolver(
  Container container)
  : base()
 {
  _container = container;
  //
  Initialize();
 }

 #endregion

 #region ' Members '

 private readonly Container _container;
 private readonly DeferredRegistrationsManager deferredRegistrations = new DeferredRegistrationsManager();
 private bool IsInitialized { get; set; } = false;

 #endregion

 private void Initialize()
 {
  deferredRegistrations.Complete();
  //
  IsInitialized = true;
 }

 #region ' IDependencyResolver '

 public override object GetService(Type serviceType)
 {
  if (!IsInitialized) return base.GetService(serviceType);

  try
  {
   return _container.GetInstance(serviceType);
  }
  catch (ActivationException)
  {
   // Despite the SimpleInjector container that throws ActivationException when 
   // service is not registered, the SignalR internal services expects dependency
   // resolver returns null in such cases.
   return null;
  }
 }

 public override IEnumerable<object> GetServices(Type serviceType)
 {
  if (!IsInitialized) return base.GetServices(serviceType);

  try
  {
   return _container.GetAllInstances(serviceType);
  }
  catch (ActivationException)
  {
   // Despite the SimpleInjector container that throws ActivationException when 
   // service is not registered, the SignalR internal services expects dependency
   // resolver returns null in such cases.
   return null;
  }
 }

 public override void Register(Type serviceType, Func<object> activator)
 {
  if (!IsInitialized)  base.Register(serviceType, activator);

  // deffers the execution of the registration to the time when this is initialized.
  deferredRegistrations.Defer(serviceType, () =>
  {
   _container.Register(serviceType, activator, Lifestyle.Singleton);

   // Marcus Miris @ 06/ago/2018:
   // As classes internas do SignalR ocasionalmente requisitam Single Intances Services atrav�s
   // do m�todo `GetServices` que retorna cole��es de inst�ncias. Tal opera��o, quando realizada
   // no container do Simple Injector, gera uma Exception, requerindo do caller a utiliza��o do m�todo
   // `GetAllInstance` ao inv�s do m�todo `GetInstance`. 
   // Para mitigar tal erro, o mesmo servi�o � registrado no container como uma cole��o.
   if (!typeof(IEnumerable).IsAssignableFrom(serviceType))
    Register(serviceType, new[] { activator });
  });

 }

 public override void Register(Type serviceType, IEnumerable<Func<object>> activators)
 {
  if (!IsInitialized) base.Register(serviceType, activators);
  
  // deffers the execution of the registration to the time when this is initialized.
  deferredRegistrations.Defer(
   serviceType, 
   () => _container.RegisterCollection(serviceType, activators, Lifestyle.Singleton));
 }

 #endregion

}



Classes auxiliares:



private class DeferredRegistrationsManager
{
    private readonly Queue<(Type, Action)> _queue = new Queue<(Type, Action)>();

    public bool Completed { get; private set; } = false;
    private static readonly object completionLockObject = new object();

    /// <summary>
    ///     Submete a action de registro para o buffer.
    ///     Caso o mesmo esteja liberado, a action � executada imediatamente.
    ///     Caso contrario, ser� liberada no invoke do m�todo `Complete`.
    /// </summary>
    public void Defer(Type serviceType, Action registration)
    {
        if (Completed)
            DoRegistration(serviceType, registration);
        else
            lock (completionLockObject) _queue.Enqueue((serviceType, registration));
    }

    public void Complete()
    {
        lock (completionLockObject)
        {
            while (_queue.Count > 0)
            {
                (Type serviceType, Action registration) = _queue.Dequeue();
                DoRegistration(serviceType, registration);
            }

            Completed = true;
        }
    }

    private void DoRegistration(Type serviceType, Action registration)
    {
        try
        {
            registration.Invoke();
        }
        catch (Exception e)
        {
            throw new Exception(
                $"Erro ao processar registro de ${serviceType?.Name}: {e.GetBaseException().Message}.",
                e);
        }
    }
}



public static class SimpleInjectorExtensions
{

    public static void RegisterCollection(
        this Container container,
        Type serviceType,
        IEnumerable<Func<object>> activators,
        Lifestyle lifestyle)
    {
        if (container == null) throw new ArgumentNullException(nameof(container));

        container.RegisterCollection(
            serviceType,
            activators.Select(activator => new TransientLifestyleRegistration(
                lifestyle,
                container,
                serviceType,
                activator)));
    }


    #region ' Inner Classes '

    /// <summary>
    ///     Replicação da inner classe homônima declarada em `SimpleInjector.Lifestyles.TransientLifestyle`,
    ///     como é possível conferir em
    ///     https://github.com/simpleinjector/SimpleInjector/blob/master/src/SimpleInjector/Lifestyles/TransientLifestyle.cs
    /// </summary>
    public class TransientLifestyleRegistration
        : Registration
    {
        private readonly Func<object> _instanceCreator;

        public TransientLifestyleRegistration(
            Lifestyle lifestyle,
            Container container,
            Type implementationType,
            Func<object> instanceCreator = null)
            : base(lifestyle, container)
        {
            ImplementationType = implementationType;
            _instanceCreator = instanceCreator;
        }

        #region ' Registration implementation '

        public override Type ImplementationType { get; }

        public override Expression BuildExpression() =>
            this._instanceCreator == null
                ? this.BuildTransientExpression()
                : this.BuildTransientExpression(this._instanceCreator);

        #endregion
    }

    #endregion

}



public class SimpleInjectorHubDispatcher
    : HubDispatcher
{
    private readonly Container _container;

    #region ' ctor '

    public SimpleInjectorHubDispatcher(
        Container container,
        HubConfiguration configuration)
        : base(configuration)
    {
        _container = container;
    }

    #endregion

    private Task WithNewAsyncScope(Func<Task> action)
    {
        var scope = AsyncScopedLifestyle.BeginScope(_container);
        return action().ContinueWith(t => scope.Dispose());
    }

    #region ' HubDispatcher '

    protected override Task OnConnected(IRequest request, string connectionId)
        => WithNewAsyncScope(() => base.OnConnected(request, connectionId));

    protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
        => WithNewAsyncScope(() => base.OnDisconnected(request, connectionId, stopCalled));

    protected override Task OnReceived(IRequest request, string connectionId, string data)
        => WithNewAsyncScope(() => base.OnReceived(request, connectionId, data));

    protected override Task OnReconnected(IRequest request, string connectionId)
        => WithNewAsyncScope(() => base.OnReconnected(request, connectionId));

    #endregion
}

Como configurar no Owin:




public static void UseSocket(this IAppBuilder app, string mapName, Container container)
{
    GlobalHost.DependencyResolver = new SimpleInjectorContainerAsSignalRDependencyResolver(container);
    app.MapSignalR<SimpleInjectorHubDispatcher>(mapName, with(new HubConfiguration(), _ =>
        {
            _.EnableJSONP = true;
            _.EnableDetailedErrors = true;
            _.EnableJavaScriptProxies = true;
        }));
}


terça-feira, 31 de janeiro de 2017

StackOverflowException e Tail Call Optimization

Dado o seguinte código a ser executado em um Console Application...

    public static void Main(string[] args)
    {
        try
        {
            Action @do = null;
            @do = () =>
            {
                WriteLine("Hellow World!");
                @do();
            };
            @do();
                
        }
        catch (Exception ex)
        {
            ForegroundColor = ConsoleColor.Red;
            WriteLine(ex);
            ResetColor();
        }

        WriteLine("Pressione  para continuar.");
        ReadLine();
    }

... se o executarmos como Any CPU, o código estourará StackOverflowException. Note, entretanto, que este tipo de exception não é capturado pelo Try-Catch. Isto corre pois StackOverflowException é uma exceção não gerenciada. Por consequencia ela leva ao encerramento abrupto do processo.

Agora, se executarmos o mesmo código em Release/x64, a exception não ocorre. Há duas razões pelas quais isso acontece:
  1. A função Do(int) é classificada como um Tail-Call Function
  2. Ao compilarmos em Release/x64, o compilador inclui otmizações no código para Tail-Call functions, removendo a recursividade.
Uma Tail-Call function é uma função cuja última operação realizada é a chamada de uma outra função. Neste caso, como Do é recursiva, ela é classificada como `Tail-Recursion Function`.

Vamos então, a efeito de testes, fazer uma modificação no nosso código.

    @do = () =>
    {
        WriteLine("Hellow ");
        @do();
        WriteLine("world!");
    };
    @do(1);

Ao executar o código agora em Release/x64, novamente é gerado StackOverflowException! E fica a pergunta: Porquê? Porque ao incluirmos o WriteLine, descaracterizamos a função Do como uma Tail-Call Function.

Loops infinitos por si só já são um perigo para as aplicações. Apesar disso, temos reações diferente ao usarmos ou não Tail-Call functions: Num loop infinito, consome-se recursos computacionais. Desta forma, ao usar Tail-Call functions seu processo só vai abendar quando não houver mais memória, ou ainda que a alocação de memória adicional não seja realizada pela função, ainda sim o consumo do processador levará a máquina a instabilidade ou lentidão. Imagine quão devastador seria ter algumas threads no IIS rodando loops infinitos em backgroud! Caso abendasse (no caso de não serem usadas Tail-call functions), estes efeitos colaterais não ocorreriam.

Utilizar tail-call functions nos livra da preocupação da profundidade do stack em casos legítimos.
Com stack overflow ou não, a Microsoft recomenta que "devemos escrever nosso código para detectar e prevenir stack overflow. Por exemplo, se sua aplicação depende de recursão, use um contador ou uma condição de estado para finalizar o loop recursivo." (https://msdn.microsoft.com/en-us/library/system.stackoverflowexception)

Escrevendo Tail-Call Functions

Por exemplo, tomemos por exemplo uma função que calcule o fatorial:

    private static int Fatorial(int i)
    {
        if (i == 1) return 1;
        return i*Fatorial(i - 1);
    }

Reparem que esta função não é Tail-recursive, pois a ultima operação realizada é multiplicação. Para transformar esta função em uma Tail-Call function nós podemos usar algumastécnicas de programação funcional:

Técnica 1 - Accumulator Passing Style

Esta estratégia basea-se em passar uma variável acumuladora para o método. Poderíamos transformá-la em tail recursive, informando um acumulador no argumento da função.

    private static int Fatorial(int i, int acc == 1)
    {
        if (i == 1) return acc;
        return Fatorial(i - 1, acc * i);
    }

Infelizmente esta abordagem acrescenta um argumento não-funcional na nossa função. Reparem que preciso invocá-la passando o argumeto opcional 1. Porém isso pode ser contornado:

    private static int Fatorial(int i)
    {
        Func<int, int, int> _fatorial = null;
        _fatorial = (x, acc) => x == 1 ? acc : _fatorial(x - 1, acc*x);
        return _fatorial(i, 1);
    }

Técnica 2 - Continuation Passing Style

A segunda estratégia resume-se em passar o ponteiro de uma função que será usado na continuação da rotina. É uma espécie de callback.

    public static int Fatorial(int n)
    {
        Func<int, Func<int, int>, int> _fatorial = null;
        _fatorial = (_n, continuation) =>
        {
            return _n == 1
                ? continuation(1) 
                : _fatorial(_n - 1, result => continuation(result * _n));
        };
        return _fatorial(n, result => result);
    }

Mais informações:




sexta-feira, 13 de janeiro de 2017

Um pouco sobre Functional Programming

Fiz uma pesquisa rápida sobre Functional Programming, e abaixo fiz uma pequena compilação do que encontrei. Alguns dos trechos de texto dispostos aqui forma copiados de sites, e acredito que todos estão referenciados nos links.

REGRAS BÁSICAS

  • Não há variáveis em Functional Programming;
  • Recursão é usada ao invés de estruturas de loop;
  • Imutabilidade cria códigos mais simples e seguros;

PURE FUNCTIONS

  • Operam apenas sobre seus parâmetros de entrada;
  • Devem retornar alguma coisa (nunca são void);
  • Sempre produzem o mesmo output quando dados os mesmos inputs;
  • Não há efeitos colaterais


HIGHER ORDER FUNCTIONS

Higher-order functions são funções que (1) recebem funções em seus argumentos; ou (2) retornam funções.

e.g.:
Func<int, bool> isEven = x => x % 2 == 0;
Func<int, bool> not(Func<int, bool> f) = x => !f(x);

Func<int, bool> isOdd = not(isEven); <-- HIGHER ORDER FUNCION


Mais informações:


MEMOIZATION

Reutiliza o resultado de execuções prévias

Mais informações:



COMBINATORS / COMPOSITION

São Higher-Order Functions que usam somente funções e outros combinators para entregar novas funcionalidades. É uma espécie de decorator para função, ampliando uma função original.

e.g.:
Func<int, int, int> soma = (x, y) => x + y;

/* este método é um combinator */
Func<int, int, int> logarOperacao = (Func<int, int, int> f) => {
 return (x, y) => {
  var result = f(x, y);
  Console.WriteLine($"Resultado: { result }");
  return result;
 }
}

// combina.
soma = logarOperacao(soma);
soma(1, 2);
soma(3, 4);

Mais informações:


CURRYING / PARTIAL APPLICATION


e.g CURRYING.:
Func<int, int, int> add = a => b => a + b;
Console.Write(add(2)(5));  // == 7!


P) qual a vantagem?
R) autoriza a utilização de uma técnica chamada "Partial Application";


PARTIAL APPLICATION


Permite que criemos uma nova função com base em uma existente. Geralmente, partimos de uma função mais genérica para criar uma função com aplicação mais expecífica.
Func<int, int, int> add = (a, b) => a + b;
Func<int, int> add2 = a => add(a, 2);

Mais informações:

quinta-feira, 10 de novembro de 2016

DDD - Rule of Thumbs #1 - Aggregate/Bounded Context Transaction Atomicity

Vou procurar listar aqui no blog algumas das Rule of Thumbs do DDD aos poucos. Hoje vou começar falando sobre Atomicidade dos Aggregates e Bounded Contexts.

Vaughn Vernon diz em seu livro "Implementing DDD":
"A properly designed Aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction. And a properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases. (...)
Limiting modification to one Aggregate instance per transaction may sound overly strict. However, it is a rule of thumb and should be the goal in most cases. It addresses the very reason to use Aggregates."
  O que aprendemos aqui de prático? Que...

[1] Se uma agregação precisa sofrer duas operações para manter consistência, então ela não está devidamente construída.

Por exemplo:

venda.AdicionarItem(novoItem);
venda.ValorTotal += novoItem.SubTotal;

No código acima, se ao adicionar o item o valor total não for computado internamente, nossa agregação entrará num estado inconsistente.

[2] Embora não seja o contexto, podemos concluir pela mesma lógica que um Application Service deve realizar uma única operação por Bounded Context, seja diretamente em uma agregação ou através de uma única chamada de serviço.

quinta-feira, 20 de outubro de 2016

Entity Framework Discriminator Index

I create a nuget package with a Entity Framework convention that allow us to create indexes on discriminator columns.

Nuget Package: https://www.nuget.org/packages/EntityFramework.DiscriminatorIndex/
GitHub: https://github.com/marcusmiris/EntityFramework.DiscriminatorIndex

Setup

Migration Configuration file:

internal sealed class Configuration : DbMigrationsConfiguration
{
    public Configuration()
    {
        this.AddSupportToDiscriminatorIndex();
    }
}

DbConfiguration:

public class DbConfiguration : System.Data.Entity.DbConfiguration
{
    public DbConfiguration()
    {
        SetMetadataAnnotationSerializer(
            DiscriminatorIndexAnnotation.AnnotationName,
            () => new DiscriminatorIndexAnnotationSerializer());
    }
}

How To Use?

Entity Type Configuration

To configure a Discriminator index on an entity, we can use the extention method HasIndexOnDiscriminator() of the EntityTypeConfiguration:

public class DbContext: System.Data.Entity.DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity().HasIndexOnDiscriminator();
        base.OnModelCreating(modelBuilder);
    }
}

Optionally, we can customize the column name, or also set the index name.

modelBuilder.Entity().HasIndexOnDiscriminator(
    columnName: "MyDiscriminator", 
    indexName: "IX_Entity_Discriminator");

Convention

Using the convention, all columns named as "Discriminator" in the database will be indexed.

public class DbContext: System.Data.Entity.DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add();
        base.OnModelCreating(modelBuilder);
    }
}

Optionally, we can provide an alternative column name to search for:

modelBuilder.Conventions.Add(new DiscriminatorIndexConvention(
    discriminatorColumnName: "CustoDiscriminator"));

When using the convention, we can also ignore some entity:



modelBuilder.Entity()
    .HasTableAnnotation(DiscriminatorIndexAnnotation.AnnotationName, null);

... or yet replace the default convention using the HasIndexOnDiscriminator extension method.

sexta-feira, 12 de agosto de 2016

Um bate-papo sobre DDD

O texto abaixo foi escrito por Robson Castilho.
Link para o post original: https://robsoncastilho.com.br/2016/06/03/um-bate-papo-sobre-ddd/


Cara, estou trabalhando num projeto DDD!!
“Projeto DDD”? Bacana. Que negócio é esse?
Então…. tem uma separação em 4 camadas (Presentation, Application, Domain e Infrastructure) e um negócio de manter o ‘Domain’ separado de detalhes técnicos, como persistência.
Hmmm..arquitetura em camadas…e o que mais?
Uma série de patterns legais, como Value Object, Repository e Dependency Injection. Está sendo um desafio e tanto configurar tudo certinho no container de DI. Dá um certo trabalho entender com isso funciona.
Legal. E o que mais tem nesse DDD? Aliás, o que significa essa sigla?
A sigla significa Domain-Driven Design. Mais uma porção de coisas, como utilizar interfaces para desacoplar as camadas e também o uso do Entity Framework como ORM para cuidar da persistência dos objetos.
É outro grande desafio entender e configurar corretamente o Entity para lidar com o mapeamento dos objetos, lazy/eager loading, migrations,…. estou aprendendo bastante!
Imagino mesmo. Parece bem interessante. Me fala mais sobre o ‘Domain’. Ainda não tô entendendo onde isso tudo se relaciona com o nome ‘DOMAIN DRIVEN DESIGN’….
(Pensativo) Hmmm…bem…o ‘Domain’ é a camada onde ficam as regras de negócio. Nela, estamos usando o chamado ‘Modelo Rico’, ou seja, trabalhamos com um modelo orientado a objetos e, como eu disse antes, nosso ‘Domain’ é totalmente ignorante de detalhes de infraestrutura, ele só conhece as regras de negócio.
Isolar o que o seu software É (a área de conhecimento que ele atende) de detalhes técnicos, frameworks, databases, é uma ótima prática, sem dúvidas. Digamos que isso seja “implementar sua arquitetura de forma correta”, como, há décadas, explanado por diversos autores (vide ‘Ports and Adapters’, ‘Onion Architecture’, ‘Clean Architecture’ e similares).
E quem teve a ideia de utilizar DDD neste projeto?
Nós mesmos (os devs)!
E por que escolheram DDD?
(Pensativo) Bom, vimos na comunidade que muita gente está falando sobre o assunto, como uma boa prática. Estamos pensando em iniciar outro projeto com DDD também.

(Ficando preocupado) E do que se trata esse novo projeto?
É um projeto ASP.NET Web API. Já estamos estudando como usar DDD com ele.
(Notando que o buraco está ficando maior) Cara, estamos falando de muitas tecnologias e termos técnicos, mas, até o momento, não citamos muitos conceitos cruciais do DDD, sabia? Você já leu o livro do Evans? Alguém do projeto tem alguma experiência com DDD?
(Confuso) Não. Estou querendo comprar. Estamos seguindo alguns artigos e códigos de exemplo. E estamos todos aprendendo sobre o assunto neste projeto. Que pontos cruciais são esses? Então, você já sabia o que era DDD, né espertinho?
Sim. Estava te testando!! Faz 7 anos que estudo e pratico DDD. Aprendi bastante coisa e fiz inúmeras bobagens também, então, acho que sei um pouquinho sobre esse negócio.
Sinta-se tomando a pílula vermelha, ok?
OK!
Vamos lá! Tecnologias são importantes (e podemos bater um papo sobre elas em outro momento), mas tenho notado que DDD tem sido mencionado na comunidade como uma espécie de tecnologia – Web API + DDD, ASP.NET + DDD, TDD + DDD e similares – e as conversas sempre acabam indo para o lado de ferramentas (ORM), padrões (DI, Repository) e arquitetura (camada disso ou daquilo).
Isso me deixa bem preocupado. Preocupo-me pela possibilidade de estarem “usando DDD” por “estar na moda”, esquecendo-se daqueles tais pontos cruciais que mencionei…
Que seriam…?
DDD é um conjunto de práticas com o objetivo de construir um software de qualidade, que atenda as necessidades do cliente. Junto dessas práticas, temos a importância de trabalharmos próximos aos domain experts (pessoas da área de negócio que possam compartilhar conhecimento com os devs), entendendo a área de negócio do cliente e quebrando o problema em problemas menores.
Nesse processo, identificamos o core business do cliente e sub-áreas de negócio de menor importância, e podemos dar maior foco ao core business.
Foco no que importa mais para cliente -> Entregamos mais valor -> Melhor retorno sobre o investimento para o cliente!
Temos, aqui, conceitos como “Ubiquitous Language”, “Subdomains” e “Bounded Contexts” e diversas práticas em nível estratégico para trabalharmos com vários times, cada um atendendo uma parte do negócio.
No DDD, o foco está no domínio, que é a área de conhecimento para a qual você está criando uma solução, e na abstração deste domínio em 1 ou mais modelos, que resolvem problemas daquele domínio.
Tudo que mencionei agora são elementos fundamentais no DDD.
(Fazendo uma cara de “que conversa é essa?”)
Claro, ainda é software, e acaba sendo inevitável entrarmos em detalhes técnicos.
O Evans fala sobre arquitetura em camadas (as 4 das quais você falou no início da conversa) e também sobre patterns para ajudar no design do domain model (Entities, VOs, Services, etc..), os chamados “Building Blocks” do DDD.

Em livros mais recentes de DDD, fala-se mais ainda sobre implementação e arquitetura, entrando em práticas de integração (REST, messaging), CQRS/Event Sourcing, etc. Com isso, os autores pretendem entrar em algo mais prático, de modo que os leitores tenham uma ideia de como implementar o software seguindo os princípios do DDD. Assim, o assunto não fica extremamente abstrato e cansativo, como em muitas partes do livro do Evans.
No entanto, mesmo estes livros que focam bastante em código e integração, não se esqueceram dos pontos cruciais do DDD.
Hmm…
Veja, no início, também cometi muitos erros. Um dos principais erros era focar bastante no “micro”, o design do modelo e os padrões (Entities, VOs e afins), e esquecer do “macro”, identificar os subdomains e delimitar os contextos.
O domínio de uma técnica só vem através de EXPERIÊNCIA, o que não se ganha da noite para o dia. Com isso, chegamos à minha segunda preocupação com essa “moda do DDD”, se você ainda estiver com tempo de me ouvir…
Pode falar!
Um time inexperiente pode não dar muita atenção ao que eu disse sobre os pontos cruciais. É normal que a “gurizada”, como você (não se sinta ofendido), foque em aprender a usar as libs e frameworks do momento, sem se perguntar muito se aquilo resolve o problema.
DDD possui um conjunto grande de práticas, que servem para resolver um problema complexo. Entender e aplicar DDD corretamente não é o mesmo que aprender a usar aquela lib de data binding.
Minha preocupação é estarem iniciando projetos tentando aplicar DDD e estarem criando aberrações. DDD foi feito para simplificar e não complicar o que é simples!
Sendo assim, é fundamental que o time seja experiente e esteja extremamente empenhado em aprender DDD e entender o negócio. Por “time experiente”, entenda que seja um time onde haja, no mínimo, um líder que possa guiar o restante. Quanto maior e mais complexo o problema, mais necessária a presença de outros devs seniors.
E claro, a presença de Product Owners/Analistas de Negócio experientes também é fundamental.
Captou?
Acho que sim, e você me deixou com medo! Temos muitos problemas técnicos e nem todos são tão empenhados assim em estudar….
Desculpa, cara. Minha intenção não era te assustar ou ainda insinuar que vocês não são capazes. A intenção é ALERTÁ-LO a respeito das coisas que eu mencionei anteriormente.
Fazer software não é algo trivial e bobo. É preciso agir e tomar decisões com responsabilidade (afinal, alguém está pagando por ele e esperando por um ótimo produto).
Dá uma refletida no assunto e, se quiser, estou à disposição pra conversarmos mais em outros momentos.
OK. Irei pensar sim! Vou processar tudo que você falou e, com certeza, iremos trocar uma ideia!
Será um prazer!
E não esqueça que DDD é simplificar, é entregar valor pro seu cliente com uma solução altamente alinhada com a área de negócio dele.
Eu não entrei em muitos detalhes – e até várias outras disfunções encontradas quando falamos sobre DDD – mas ocasiões não irão faltar!
Com certeza. A gente se fala! Até mais e muito obrigado pelas dicas!
“Que isso”! Até!

segunda-feira, 21 de setembro de 2015

Configurando Web Deploy corretamente.

1) Ativar a Role `Management Service` @ Server Manager. Este setup instalará no Windows o serviço `Web Management Service`.



2) Instalar o Web Deploy (http://www.iis.net/download/webdeploy). Este setup instalará o serviço de windows `Web Deployment Agent Service`.

Obs.: É importante realizar este setup apenas após a instalação do `Web Management Service`, pois de outra forma não será disponibilizada a opção `IIS Deployment Handler` no Custom Setup.

3)  ativar `Enable remote connections` @ Management Service @ IIS Manager, e iniciar o serviço `Management Service (WMSVC)`


quarta-feira, 12 de agosto de 2015

quinta-feira, 25 de junho de 2015

CSS3: 'display: flex' para simular 'height: 100%' dentro de um container

O atributo 'height: 100%' nem sempre se comporta como gostaríamos que se comportasse. Por exemplo, não dá pra fazer com que um div que é o último nó dentro de um container ocupe o espaço restante. No CCS3 a coisa ficou bem mais fácil: existe a marcação 'display: flex', que quando aplicado à um container, faz com que seus filhos imediatos se transformem blocos dentro do container. Abaixo segue um exemplo. HTML:
<div id="container">
    <div>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
        <p>I will expand to fill extra space</p>
    </div>
<div>
HTML:
html, body {
    height: 100%;
    width: 100%;
    margin: 0px;
}

#container {
    height: 100%;
    width: 100%;
    min-height: 0px;
    display: flex;
    flex-direction: column;
}

#container > * {
    border: 1px solid black;
    margin: 0px;
}

#container > *:first-child {
    flex: 1;
    overflow-y: auto;
}

JSFiddler: http://jsfiddle.net/e8n3hLmg/3/


Obs:
  • para o scroll funcionar, foi necessário aplicar o atributo 'min-height' no container. Sem isso o scroll não funcionaria. no Stack Overflow (http://stackoverflow.com/a/28639686) tem uma resposta que explica o porquê.

quinta-feira, 22 de janeiro de 2015

Private Symbol Server + NuGet Symbol Packages

Num determinado momento durante o desenvolvimento do projeto no qual estou alocado no momento, eu quis aproveitar para depurar um pacote do NuGet que instalei no meu projeto, pacote este ao qual fui eu mesmo quem o criou. Eu quis aproveitar o cenário que já estava instaurado no momento da depuração do projeto para tentar encontrar um possível bug no componente do pacote. Porém isto não foi possível pois o pacote distribuído e referenciado continha apenas o assembly (.dll), e para a realização da depuração faz-se necessário a existência do arquivo de símbolos (.pdb).

Apesar da possibilidade de atender a este problema com uma solução em particular (e.g. copiando o .pdb manualmente), eu aproveitei para tentar encontrar uma solução madura para casos como este. O primeiro pensamento foi em convencionar a distribuição dos arquivos .pdb's dentro dos respectivos pacotes do NuGet, mas isso não me pareceu razoável como uma solução definitiva, pois assim os arquivos de simbolos também seriam disponibilizados no ambiente de produção, o que pra muitos é considerado inseguro, pois em casos específicos algumas informações valiosas sobre a construção da aplicação poderiam ser exploradas indevidamente.

Assim, depois de fazer uma pesquisa sobre o assunto, concluí que a alternativa seria configurar um Symbol Server Privado para ficar disponível no ambiente de desenvolvimento. Este Server fica então responsável pelo armazenamento dos arquivos de símbolos, e o depurador fica configurado para consultá-lo sempre que precisar depurar um assembly.

Apesar da solução ser simples, a minha pesquisa não o foi. Por que? Porque, de um lado, eu encontrei materiais explicando como configurar um Symbol Server para ser consumido pelo Visual Studio (WinDbg); do outro, encontrei como gerar Symbol Packages do NuGet para ser publicados no SymbolSource.org, mas não encontrei como utilizar um Symbol Package do NuGet num Symbol Server privado. Minha dúvida era: o depurador faria o download do arquivo .symbol.nupkg e saberia extrair o .pdb?

Uma vez que não consegui encontrar em uma única página na internet a resposta centralizada, onde eu pudesse concluir a dinâmica da coisa, decidi escrever este post. Então, vamos começar pelo princípio.

Conceito Básico de um Symbol/Source Server

Antes de tudo, apesar do nome bonito, um Symbol Server basicamente não passa de um diretório onde um depurador pode procurar pelos arquivos .pdb. Basicamente nestes arquivos ficam armazenadas as informações necessárias para a depuração, fazendo um mapeamento entre o binário do assembly e as linhas no script no arquivo fonte. 

Quando depuramos a nossa aplicação, o depurador realiza uma busca a um arquivo .pdb, que possua o mesmo nome do assembly (e.g. se o assembly é xpto.dll, então ele procura por xpto.pdb). Esta busca é realizada basicamente na seguinte ordem:
  • no diretório do assembly;
  • no diretório do windows;
  • no diretório %windir%\symbols\dll
  • no diretório %windir%\dll
  • no cache do Visual Studio (configurado em Options > Debugging > Symbols)
  • no Symbols Locations configurados no Visual Studio (configurado em Options > Debugging > Symbols)


Isso significa que se eu criar um diretório c:\temp\symbols, colocar ali o arquivo 'assembly.pdb' e configurar este diretório como um Symbol Location no Visual Studio, quando eu depurar 'assembly.dll' referenciada na minha aplicação o Visual Studio vai encontrar o pdb/symbols.

Neste momento os símbolos estariam carregados, mas isso ainda não quer dizer que conseguríamos depurar o nosso assembly. Assim seria porque o que o depurador encontraria no .pdb é apenas a referencia de onde está localizado o arquivo fonte utilizado na compilação. E aqui está algo que é um grande problema nesses arquivos: as referências a estes arquivos estão armazenados usando um caminho absoluto (e.g. C:\meuprojeto\minhaclasse.cs). Isso leva a dois problemas: O primeiro é que, se nós compilássemos o assembly num Build Server, ao depurar na máquina de desenvolvimento o depurador não iria encontrar o arquivo fonte (obviamente partindo do principio de que nas duas máquinas o projeto está configurado em diretórios de caminhos absolutos diferente). O segundo problema acarretado é que se nós versionássemos e compilássemos um assembly, por exemplo, 3 vezes, neste caso os três arquivos .pdb gerados apontariam para o mesmo arquivo fonte físico, o que me impossibilitaria a depuração dos pacotes de versões antigas, pois eu sempre teria apenas a última versão do código em disco.

Assim, se o arquivo fonte existir na máquina de desenvolvimento, OK; caso contrário, o Visual Studio vai pedir pra você indicar a localização do aquivo.


Como montar um Symbol/Source Server Básico

Um Symbol Server de verdade precisa ter o controle das versões dos .pdb's de um assembly, e atuar também como um Source Server, contendo o código fonte correto para a versão do assembly. Uma vez que o código fonte é carregado junto com o .pdb, é necessário ajustar a referência absoluta que estes aquivos possuem ao fonte. Este processo é chamado de  Source Indexing. Mais à frente eu explico como este processo pode e será realizado; permita-me por hora ater apenas à teoria.

Depois da indexação do fonte, nós precisaríamos organizar o nosso Symbol Server (que por hora é um diretório numa pasta, lembra)? por versão. Lembra-se do processo de busca que o depurador faz para encontrar o .pdb? Então... tem algo que eu não disse: em cada um dos diretórios é possível organizar da seguite forma: .\{assembly.pdb}\{GuidDoAssembly}\assembly.pdb. Assim o depurador saberá como encontrar a versão correta: pelo Guid do assembly.

A boa notícia é que não precisamos fazer isso "na mão". A Microsoft disponibiliza um conjuto de ferramentas (console) chamada Debugging Tools for Windows (WinDbg). Nele tem várias coisas, mas não é necessário instalar tudo, como é possível entender pela imagem abaixo.


Neste setup são instaladas ferramentas para indexação do fonte, montagem e consulta à um symbol store (server). Adicione o caminho da instalação do path do windows. Depois, para montar um Symbol Store de exemplo, use o seguinte comando no console:

symstore add /f Vvs.Nfe.TecnoSpeed.pdb /s "C:\PackageServer\symbols\" /t "Meu Produto"

Como resultado o .pdb (sem os fontes) será adicionado ao nosso symbol server, como pode ser visto conforme a imagem abaixo:


Assim, adicione o path na lista de Symbol Locations do Visual Studio e o depurador encontrará o .pdb correto para o assembly. Assim temos um Symbol server básico, porém ele ainda não funciona como Source Server, e eu não vou montá-lo na mão (o que é possível usando o script Ssindex.cmd, porém seria impraticável), mas vou fazer tudo isso à vera ao gerar o pacote do NuGet.

Usando Symbol Package do NuGet

O NuGet cria Symbol Packages! Para mais informações quanto à isso, procure no Google. O que não me contaram é como utilizar o raio do arquivo '.symbol.nupkg' num private Symbol Server. O processo manual seria:

  1. descompactar o pacote;
  2. indexar o fonte no pdb;
  3. adicionar ao store;

Então, o que o pessoal do SymbolSource.org fez foi criar um Serviço Web MVC4 chamado SymbolSourcer Server Basic (disponível no nuget.org) que linka o push do NuGet com o processo acima, e disponibiliza o Symbol Source por http. Assim, o que eu fiz foi:


  1. Crieu uma aplicação MVC 4 em branco;
  2. Instalei o pacote (Install-Package SymbolSource.Server.Basic);
  3. Alterei o appSetting 'SrcSrvPath' no web.config para apontar para o full path do SrcSrv (que foi instalado com o WinDbg);
  4. Publiquei o site;
  5. Alterei o Pool p/ 32 bits, conforme é possível ver mais abaixo:
  6. Dei permissão ao usuário do Pool no diretório 'Data'.





Depois disso, é possível disponibilizar o symbol package da seguinte forma:

nuget push Meu.Pacote.0.0.1.symbols.nupkg -source http://{SymbolServerSiteUrl}/nuget

Depois disso, adicione um Symbol Location ao Visual Studio com a URL descrita em 'Visual Studio Debugging URL' na página no nosso SymbolSource.

Outras informações:
http://msdn.microsoft.com/en-us/magazine/cc163563.aspx
http://www.lionhack.com/2014/01/14/advanced-dotnet-debugging-pdbs-and-symbols/

quinta-feira, 15 de janeiro de 2015

Custom errors and error detail policy in ASP.NET Web API

Hoje tentei debugar um serviço Web API que fiz deploy para um servidor remoto. O serviço estava retornando um erro http 500, sem os detalhes da exception.

HTTP/1.1 500 Internal Server Error
Content-Length: 89
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Thu, 15 Jan 2015 17:03:10 GMT

{
   "$type":"System.Web.Http.HttpError, System.Web.Http",
   "Message":"An error has occurred."
}

A primeira coisa que tentei foi desabilitar os Custom erros no web.config:

<customErrors mode="Off" />

...porém isso não mudou nada. Depois de pesquisar bastante eu percebi que o Web API usa uma configuração diferente para detalhar os erros das exceptions.

GlobalConfiguration.Configuration.IncludeErrorDetailPolicy
  = IncludeErrorDetailPolicy.Always;

A diferença na configuração é porque o CustomErros do web.config é algo que o ASP.Net usa para determinar se aquela tela de erro amarela deve ou não exibir os detalhes da exception. Já o Web API configura se os detalhes de HttpError serão serializados.

Apesar disso, algo que percebi é que eu não deveria estar tendo toda esta dor de cabeça, pois segundo o MSDN...
Use the default behavior for the host environment. For ASP.NET hosting, use the value from the customErrors element in the Web.config file. For self-hosting, use the value LocalOnly.
(
http://msdn.microsoft.com/en-us/library/system.web.http.includeerrordetailpolicy(v=vs.118).aspx)
... assim ainda tem alguma peça que não está encaixando. Mais tarde eu procuro saber o porquê.

Resultado final:

HTTP/1.1 500 Internal Server Error
Content-Length: 0
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-Powered-By: ASP.NET
Date: Thu, 15 Jan 2015 19:44:04 GMT

{
   "$type":"System.Web.Http.HttpError, System.Web.Http",
   "Message":"An error has occurred.",
   "ExceptionMessage":"...",
   "ExceptionType":"...",
   "StackTrace":"..."
}

Outras informações @ Per Request Error details policy in ASP.Net Web API.

terça-feira, 15 de julho de 2014

Afinal de contas, você é júnior, pleno ou sênior? E por quê?

O texto abaixo foi escrito por André Carlucci. Link para o post original: http://www.andrecarlucci.com/dev/afinal-de-contas-voce-e-junior-pleno-ou-senior-e-por-que/

Quem não gostaria de ser “expert” em um assunto? De saber intuitivamente qual é a resposta?
Nestes anos todos de Way2, devo ter entrevistado centenas de pessoas e sempre gostei de fazer a seguinte pergunta:

Afinal de contas, você se considera júnior, pleno ou sênior? E por quê?

Acho que nunca recebi uma resposta com propriedade e o mais comum é encontrar júniors que acham que são sêniors, plenos que são júniors e diversas variações.
Não me leve a mal, nem eu sei direito como responder a essa pergunta e hoje resolvi compartilhar o que penso no intuito de receber algum feedback sobre o assunto.
Uma coisa que se escuta falar muito no mercado é uma definição temporal do assunto:
  • 0 a 2 anos de trabalho: você é júnior
  • 2 a 4 anos: pleno
  • mais de 4: sênior
Nunca gostei dessa classificação. Me desculpe, mas 4 anos não é nada no mundo da TI. Não dá tempo de se atingir a maturidade do que eu acho que seria um sênior, de apanhar um pouco de projetos, de errar o suficiente para aprender e se tornar um expert. E também, como comparar alguém com 4 e alguém com 10 anos experiência em software? Ambos são sêniors e pronto?
Falar em 10 anos de prática me lembra de Anders Ericsson e sua tese de que você precisa de no mínimo 10.000 horas de prática em uma habilidade para dominá-la de verdade. Essa ideia foi popularizada no livro Outliers , de Malcolm Gladwell (que por sinal, recomendo) e você pode encontrar exemplos sobre ela em muito lugares na rede.
Mas será que só tempo praticando importa? Anders fala muito de repetição, mas na minha opinião não é repetição e sim o “tentar em um nível mais difícil”, errar, modificar sua estratégia e aprender o que nos faz evoluir para um novo patamar. Pra mim, alguém que trabalha o mesmo ano 10 vezes tem 1 ano de experiência e não uma década. Com nosso mercado aquecido e muito mais vagas do que desenvolvedores, é comum a pessoa estacionar no aprendizado e fazer mais do mesmo por muito tempo, sem se preocupar em evoluir ou inovar em qualquer direção. Não gostou? Me demite, tem 200 vagas abertas só nesse bairro.
Na Way2 a gente trabalha com times bem pequenos e eficientes. A cultura do fazer sempre mais e buscar o melhor é a regra e alguém acomodado não se encaixaria em um ambiente assim. Cada pessoa aqui dentro é capaz de escrever sozinho um sistema rápido, testável e desacoplado com suporte pra 200K requisições por segundo usando somente o notepad e uma versão otimizada por ele mesmo do MSBuild. Mentira. Mas os caras correm atrás.
Certo, mas se o tempo não me diz tudo sobre a qualidade do profissional, o que nos diz? Em outras palavras, quais são as características importantes que podemos reconhecer em alguém e como podemos agrupá-las em níveis de maturidade?

O modelo Dreyfus

Em 1980 os irmãos Stuart e Hubert Dreyfus escreveram um paper de 18 páginas a respeito de suas pesquisas na Universidade de Berkeley, na Califórnia (você sabe o país). Eles propuseram um modelo de aquisição de habilidades onde o estudante passa por 5 estágios distintos de proficiência em uma habilidade específica: novato, iniciante avançado, competente, proficiente e especialista.
Este modelo não olha para uma pessoa como um todo estando em um estágio. Na realidade, estamos em diferentes estágios dependendo de qual disciplina estamos falando. Você provavelmente é especialista em leitura ou em caminhar: você apenas o faz, não pensa em cada letra lida ou cada passo dado, é tudo muito natural, faz parte de você. Já não podemos dizer a mesma coisa sobre dirigir, principalmente se a placa do seu carro é de São José (ou qualquer outra cidade perto da sua que queira fazer a piada). Em outras palavras, o modelo Dreyfus é uma classificação por habilidade.
Segue então os 5 níveis, de acordo o paper original mencionado acima:

Estágio 1: Novato


Novatos por definição tem pouca ou nenhuma experiência no assunto em questão. Eles estão extremamente preocupados com o sucesso da tarefa e não sabem bem se tudo vai dar certo ou não.
Novatos não necessariamente estão interessados em aprender e sim em completar a tarefa que lhes foi dada. Eles não conseguem ainda improvisar e podem se confundir facilmente se as coisas saírem dos trilhos.
Por outro lado, eles podem suceder se lhes for dado um lista de regras independentes de contexto e nada fora disto aconteça. Em outras palavras:
“Novatos precisam de regras específicas”
Como lidar com novatos? Simples, tenha regras específicas para eles seguirem. Além disso, tenha sempre uma pessoa mais experiente para que eles possam se consultar, pois quando o contexto destas regras mudar por fatores externos, eles terão problemas em adaptar e improvisar o que eles já sabem para resolver o problema.

Estágio 2: Iniciante Avançado 


O próximo passo de um novato é se tornar iniciante avançado. Uma pessoa neste nível já mostra um pouco de segurança para adaptar as regras um pouco e realizar tarefas sem ajuda de outras pessoas.
Iniciantes avançados querem a solução de forma rápida. Você já passou por isso, é aquela sensação de quando você encontra um novo framework e tudo que faz é escanear rapidamente a documentação a procura da função ou linha de código que resolve o problema. Achou? Control+c, alt+tab, control+v. The end. Provavelmente a maioria dos web developers são iniciantes avançados em jQuery.
“Iniciantes avançados querem informação de forma rápida”
Neste nível o desenvolvedor ainda tem dificuldades com troubleshooting, mas já pode resolver alguns problemas usando o que já aprendeu em situações parecidas no passado. Ele não quer entender a visão geral, apenas resolver aquele problema e se você tentar explicar a teoria de forma mais aprofundada, ele provavelmente vai achar aquilo irrelevante.

Estágio 3: Competente


Neste nível desenvolvedor já consegue criar e trabalhar com modelos conceituais da tarefa a ser desenvolvida. Ele já é capaz de descobrir por que as coisas não estão funcionando e enfrentar problemas completamente novos. Apesar de ainda ter dificuldade em determinar onde focar seus esforços, seu trabalho é baseado em planejamento e experiência.
“Competentes sabem fazer troubleshooting”
Desenvolvedores neste nível são normalmente descritos como pessoas com iniciativa e prontos para ajudar. As vezes eles tomam posições de liderança (formal ou não) de seu time de desenvolvimento. É um ótimo estágio para se ter trabalhando em sua empresa, pois eles tendem a ajudar bastante os novatos e não incomodam muito os experts, apesar de pedirem sua ajuda para tarefas complexas demais.

Estágio 4: Proficiente


Proficientes precisam da visão global. Eles vão atrás da informação como um todo até descobrir como tudo funciona por trás das camadas de abstração. Normalmente se frustam com explicações simples demais.
Proficientes conquistaram a característica mais importante do modelo Dreyfus: eles tem a capacidade de analisar e corrigir performances ruins do passado . Eles refletem a respeito de como desempenharam uma tarefa e aperfeiçoam sua estratégia para uma próxima vez. Até este estágio, esta capacidade simplesmente não está disponível.
Da mesma forma, proficientes aprendem com os erros dos outros. Eles leem case studies, estudam por que projetos não deram certo e melhoram, mesmo não tendo participado diretamente do ocorrido.
Outra característica deste estágio é a capacidade de aplicar "máximo", que São verdades fundamentais que pódem ser aplicadas dadas UM determinado contexto. Por exemplo, UM "Maxim" famoso no mundo do extreme programming é: "Teste tudo que você pode quebrar". Para UM novato, isso é UMA receita. O que eu testo? Gets e Sets? Linhas simples de print? Novatos acabam testando Coisa irrelevantes.
Desenvolvedores proficientes sabem o que pode quebrar. Eles tem a experiência e o julgamento para entender que “maxims” são dependentes de contexto. E entender o contexto é a chave para se tornar um expert . Sua vivência os diz o que provavelmente vai ser a próxima coisa a acontecer e caso isso não aconteça, eles sabem o que precisa ser alterado.
Outro grande exemplo de contexto é o uso dos famosos “design partterns”. É muito comum vermos desenvolvedores em estágios anteriores aprenderem e começarem a usar os design patterns em todo lugar que eles podem. Um proficiente sabe exatamente quando, onde faz sentido e qual o ganho em utilizar cada um deles.
“Proficientes refletem com o passado e se aperfeiçoam.”
Proficientes refletem com o passado e se aperfeiçoam e esta é a característica primordial do desenvolvimento ágil. Chegar neste estágio é uma grande vitória pessoal e um proficiente está muito mais para um especialista júnior do que para um competente avançado.

Estágio 5: Especialista


Especialistas são a fonte principal de sabedoria e informação em qualquer campo. Eles são os responsáveis pelo desenvolvimento de melhores e mais eficientes métodos de se fazer as coisas. Eles são os caras que escrevem livros, artigos e continuam movendo a tecnologia pra frente.
Além de poderem aplicar sua sabedoria ao contexto da situação como ninguém, especialistas usam sua intuição mais do que a razão. Eles fazem isso a um ponto que para o resto de nós parece até mágica: “sei lá, me parece certo”.
“Especialistas usam a intuição.”

Intuição, como assim?

Não estou querendo dizer intuição como um sexto sentido ou algum poder supernatural. Um paralelo na área de medicina poderia ser quando um paciente é apresentado a um médico que diz: “hmm… ele me parece ter Síndrome de Klippel-Trenaunay, por favor, faça estes exames” e no final o paciente estava mesmo com aquela doença. É algo como o famoso “House Moment”.
De alguma forma a lista enorme de experiências, memórias e informações dentro do cérebro do médico combinadas e adaptadas ao contexto atual o fizeram chegar ao diagnóstico. Talvez ele nem mesmo saiba de forma consciente como, mas ele foi capaz de prestar atenção nos detalhes corretos e filtrar o irrelevante de uma forma que somente um expert consegue fazer.
É claro que especialistas não são perfeitos, eles podem errar como qualquer outra pessoa e com frequência vão discutir até mesmo com outro especialista da mesma área. Além disso, não necessariamente um expert é um bom professor. Ensinar é uma disciplina como qualquer outra e não há garantia que ele a possua.
Outra coisa é que é muito fácil degradarmos a performance de um especialista: basta fazê-lo seguir regras rígidas. Pode ser até regras que ele mesmo escreveu, não vai dar certo. A verdade é que temos que dar a este nível de profissional a liberdade que ele precisa: deixe-o produzir.

Chegando lá


A primeira coisa que você tem que se perguntar não é como se tornar um expert, mas sim até onde você quer chegar. Ninguém é e nem tem como ser tão avançado em tudo e a maioria das habilidades já retornam o valor que precisamos em seus primeiros níveis. Tudo depende de seu contexto.
Para as disciplinas que queremos dominar, tenha em mente que não há expertise sem experiência . O que podemos fazer é tirar o melhor dessa experiência para acelerar nosso progresso. Aprender é por si só uma habilidade que deve ser trabalhada e o auto-conhecimento é a peça principal para crescermos. Saber o quanto não sabemos sobre algo é o que vai fazer nos preparar para grandes problemas. E passar por eles.
E lembre-se: o modelo dreyfus é só uma ferramenta, não o meio. Entender em que ponto estamos e saber como lidar com cada membro de seu time de acordo com sua maturidade, restrição e habilidade é a chave para equipes fortes e de grande performance.