Compartilhando biblioteca entre projetos com o TFS
Na semana passada o amigo Ricardo Dorta me mandou uma pergunta por email que é tão comum que achei que merecia virar um post.
Lembro que quando usava Source Safe (jesus!!!), ele tinha uma opção de link, que fazia com que ao modificar um arquivo em um projeto, o mesmo arquivo em outro projeto também era modificado. Explicando o contexto, tenho algumas assemblies com classes que serão utilizadas por vários projetos, porém esses projetos tem targets diferentes (Xamarin, Unity, Windows Store, Windows Phone etc…). A solução tosca é criar todos os projetos na mesma pasta compartilhando os arquivos fisicamente. Será que o TFS me da uma solução mais “elegante”?
Em suma: Temos vários projetos de aplicações (por exemplo, uma aplicação Web e outra Windows Phone) em soluções diferentes (pois são sistemas diferentes). Entretanto, esses projetos dependem de bibliotecas comuns, que por sua vez estão em suas próprias soluções. Como lidar com isso?
O problema
Considere a estrutura de projetos abaixo. Repare que temos dois diretórios de bibliotecas (FrameworkLib1, FrameworkLib2) e dois sistemas (PhoneApp, WebApp). Os dois sistemas consomem as duas bibliotecas. Ou seja, é como se a estrutura de projetos fosse, na verdade, assim: Dessa forma, os sistemas poderiam referenciar os projetos das bibliotecas. Não apenas isso, mas seria possível também editar as bibliotecas ao mesmo tempo que damos manutenção nos sistemas.
##
O jeito obsoleto – Share do SourceSafe
O Visual SourceSafe oferecia um recurso de compartilhamento (Share, ou “link” como meu amigo chamou). Com ele, era possível fazer exatamente o que ele gostaria: “injetar” os projetos das bibliotecas na estrutura dos sistemas. Dessa maneira, criava-se uma estrutura como a da figura a seguir. Nela, quaisquer checkins feitos nos diretórios PhoneApp/Src/PhoneApp/Lambda3.FrameworkLib1 (ou FrameworkLib2) seriam refletidos no diretório original Lambda3.FrameworkLib1/Lambda3.FrameworkLib1 (ou FrameworkLib2). Essa técnica até resolveria o problema do meu amigo - independente de ser a melhor solução ou não - não fosse um pequeno detalhe: o TFS não tem o recurso de Share. No TFS temos três abordagens possíveis: uma client-side, uma server-side e uma terceira independente do TFS.
Abordagem 1: Client-side – Mapeamentos de workspace
Um recurso poderoso – e pouco explorado – do TFS é o uso do mapeamento de workspaces para baixar o código-fonte em locais específicos do nosso computador. Normalmente, as “boas práticas” recomendam que mapeemos apenas o diretório-raiz do servidor do TFS a um diretório local em nosso computador; dessa forma, garantimos a consistência da estrutura de diretórios. Mas para resolver este caso específico, iremos contra essa boa prática. Veja o que eu fiz:
- Na janela Source Control Explorer, selecionei a opção Workspaces:
- Depois, cliquei em Add para adicionar um novo workspace:
- Finalmente, configurei meus mapeamentos. Repare que peguei os diretórios dos projetos das minhas bibliotecas e “injetei-os” sob o diretório da minha aplicação. O resultado final é exatamente o mesmo que teríamos com o Share do SourceSafe. Qualquer alteração que eu faça nesses diretórios mapeados refletirá nos originais.
Prós e contras
- Prós
- Fácil de configurar – similar ao comando Share do SourceSafe;
- Não requer nenhuma alteração no servidor. A configuração é feita apenas no workspace;
- Alterações feitas nos projetos das bibliotecas são refletidas imediatamente no diretório original.
- Contras
- Por ser uma solução_ client-side_, precisa ser repetida no computador de todos os desenvolvedores;
- Mas isso pode ser simplificado com o uso de Workspace Templates;
- Como não podemos criar mais de um mapeamento por diretório, não dá para configurar PhoneApp e WebApp (nossos exemplos) ao mesmo tempo. Precisaríamos ter um workspace para cada aplicação;
- Times não podem escolher quando recebem as alterações feitas por outros times, já que estão todos apontando para o mesmo diretório compartilhado;
- Mapeamento deve ser replicado manualmente nas definições de build automatizado, senão não compila.
- Por ser uma solução_ client-side_, precisa ser repetida no computador de todos os desenvolvedores;
Abordagem 2: Server-side – Branches
O uso de workspaces pode ser bem simples, mas não dá para garantir que todos os desenvolvedores estarão com seus workspaces devidamente configurados. Se alguém estiver fora do padrão, estará feita a “caquinha”! Assim, uma alternativa mais “segura” seria definir os compartilhamentos do lado do servidor e não do lado do cliente. Dessa forma evitaríamos a necessidade de configurar o que quer que fosse nas máquinas dos desenvolvedores. E como fazer isso do lado do servidor? Ora, é simples: com branches!
- Crie uma branch para cada biblioteca:
- Aponte para dentro do diretório da solução dos aplicativos que irão consumir as bibliotecas:
- Repita o processo para cada biblioteca. O resultado final será algo assim:
Prós e contras
- Prós
- Solução server-side – não depende de configuração na máquina dos desenvolvedores;
- Mais fácil de garantir que todos estão seguindo a mesma estrutura definida no servidor;
- Permite o isolamento de alterações – ou seja, um time consegue decidir quando recebe as alterações feitas por outro time;
- Transparente para a automação de build – nenhuma configuração adicional necessária.
- Contras
- Alterações não são propagadas imediatamente. Requer Merge para transferir alterações feitas nas cópias dos aplicativos para o diretório original das bibliotecas (e vice-versa);
- Sujeito a perda de sincronia e excesso de conflitos se o time não se lembrar de fazer merges com frequência;
- Mais branches para serem gerenciados – todo branch deve ser gerenciado!
Abordagem 3: Independente – Nuget
As duas soluções anteriores, apesar de funcionarem, têm suas limitações. O que mais me incomoda nelas é o processo para a sincronização das alterações feitas nas cópias das bibliotecas (injetadas dentro das solutions das aplicações) e seus diretórios originais. Na primeira solução, baseada em workspaces, não temos controle nenhum. Já na segunda, temos algum controle (pois podemos decidir o momento do merge) mas ainda assim não é um processo 100% seguro. Por conta disso, prefiro uma solução baseada em Nuget:
- As bibliotecas (FrameworkLib1, FrameworkLib2) são empacotadas como pacotes Nuget (sugiro o uso do TFS Nugetter);
- Criamos um Feed Nuget local para hospedar os pacotes (com o Nuget Server);
- Os projetos deixam de referenciar diretamente os projetos das bibliotecas e passam a referenciar os pacotes Nuget.
Conclusão
Mas afinal de contas, qual é a melhor solução? Claro que isso depende muito do cenário de cada time, mas no geral eu tendo a recomendar a seguinte ordem de preferência: A solução baseada em Nuget, apesar de ter o custo inicial mais alto (pois depende de montagem de infraestrutura) acaba sendo, ao longo do tempo, a mais robusta. Mas, como dizem os americanos, YMMV. Um abraço, Igor
05/07/2013 | Por Igor Abade V. Leite | Em Técnico | Tempo de leitura: 5 mins.