Eu pensei sobre isso um pouco atrás e recentemente reapareceu enquanto minha loja estava fazendo seu primeiro aplicativo Java real da web.
Como introdução, vejo duas estratégias principais de nomenclatura de pacotes. (Para ser claro, não estou me referindo a toda a parte 'domain.company.project' disso, estou falando sobre a convenção de pacote abaixo dela.) De qualquer forma, as convenções de nomenclatura de pacote que vejo são as seguintes:
Funcional: Nomear seus pacotes de acordo com sua função arquitetônica, em vez de sua identidade de acordo com o domínio do negócio. Outro termo para isso pode ser nomear de acordo com 'camada'. Portanto, você teria um pacote * .ui e um pacote * .domain e um pacote * .orm. Seus pacotes são fatias horizontais em vez de verticais.
Isso é muito mais comum do que a nomenclatura lógica. Na verdade, acho que nunca vi ou ouvi falar de um projeto que faça isso. É claro que isso me deixa desconfiado (mais ou menos como pensar que você encontrou uma solução para um problema de NP), pois não sou muito inteligente e presumo que todos devem ter grandes motivos para fazer isso da maneira que fazem. Por outro lado, não me oponho a que as pessoas simplesmente sintam falta do elefante na sala e nunca ouvi um argumento real para nomear pacotes dessa maneira. Parece ser o padrão de fato.
Lógico: Nomear seus pacotes de acordo com sua identidade de domínio de negócios e colocar todas as classes relacionadas a essa fatia vertical de funcionalidade nesse pacote.
Eu nunca vi ou ouvi isso, como mencionei antes, mas faz muito sentido para mim.
Tenho a tendência de abordar os sistemas verticalmente em vez de horizontalmente. Eu quero desenvolver o sistema de processamento de pedidos, não a camada de acesso a dados. Obviamente, há uma boa chance de tocar na camada de acesso a dados no desenvolvimento desse sistema, mas a questão é que não penso nisso dessa forma. O que isso significa, é claro, é que quando eu receber um pedido de alteração ou quiser implementar algum novo recurso, seria bom não ter que procurar em um monte de pacotes para encontrar todas as classes relacionadas. Em vez disso, eu apenas olho no pacote X porque o que estou fazendo tem a ver com o X.
Do ponto de vista do desenvolvimento, considero uma grande vitória ter seus pacotes documentando seu domínio de negócios em vez de sua arquitetura. Eu sinto que o domínio é quase sempre a parte do sistema que é mais difícil de entender, já que a arquitetura do sistema, especialmente neste ponto, está quase se tornando comum em sua implementação. O fato de eu poder chegar a um sistema com este tipo de convenção de nomenclatura e, instantaneamente, a partir da nomenclatura dos pacotes saber que ele lida com pedidos, clientes, empresas, produtos, etc., parece muito útil.
Parece que isso permitiria que você aproveitasse muito melhor os modificadores de acesso do Java. Isso permite que você defina interfaces de maneira muito mais limpa em subsistemas, em vez de em camadas do sistema. Então, se você tem um subsistema de pedidos que deseja que seja transparentemente persistente, você poderia, em teoria, nunca deixar que ninguém soubesse que é persistente, não tendo que criar interfaces públicas para suas classes de persistência na camada dao e, em vez disso, empacotando a classe dao em com apenas as classes com as quais lida. Obviamente, se você quiser expor essa funcionalidade, poderá fornecer uma interface para ela ou torná-la pública. Parece que você perde muito disso por ter uma fatia vertical dos recursos do seu sistema divididos em vários pacotes.
Suponho que uma desvantagem que posso ver é que isso torna a remoção de camadas um pouco mais difícil. Em vez de apenas excluir ou renomear um pacote e, em seguida, colocar um novo no lugar com uma tecnologia alternativa, você precisa entrar e alterar todas as classes em todos os pacotes. No entanto, não vejo que seja grande coisa. Pode ser por falta de experiência, mas eu tenho que imaginar que a quantidade de vezes que você troca de tecnologia empalidece em comparação com a quantidade de vezes que você entra e edita fatias de recursos verticais em seu sistema.
Então eu acho que a pergunta seria para você, como você nomeia seus pacotes e por quê? Por favor, entenda que não acho necessariamente que tropecei na galinha dos ovos de ouro ou algo assim aqui. Sou muito novo em tudo isso, principalmente com experiência acadêmica. No entanto, não consigo detectar as lacunas em meu raciocínio, então espero que todos vocês possam para que eu possa seguir em frente.
fonte
Respostas:
Para design de embalagem, primeiro divido por camada e depois por alguma outra funcionalidade.
Existem algumas regras adicionais:
Portanto, para um aplicativo da web, por exemplo, você pode ter as seguintes camadas em sua camada de aplicativo (de cima para baixo):
Para o layout do pacote resultante, estas são algumas regras adicionais:
<prefix.company>.<appname>.<layer>
<root>.<logic>
<root>.private
Aqui está um exemplo de layout.
A camada de apresentação é dividida por tecnologia de visualização e, opcionalmente, por (grupos de) aplicativos.
A camada de aplicativo é dividida em casos de uso.
A camada de serviço é dividida em domínios de negócios, influenciados pela lógica de domínio em uma camada de back-end.
A camada de integração é dividida em 'tecnologias' e objetos de acesso.
As vantagens de separar pacotes como esse é que é mais fácil gerenciar a complexidade e aumenta a testabilidade e a reutilização. Embora pareça um monte de sobrecarga, na minha experiência, é muito natural e todos que trabalham nesta estrutura (ou semelhante) pegam em questão de dias.
Por que eu acho que a abordagem vertical não é tão boa?
No modelo em camadas, vários módulos de alto nível diferentes podem usar o mesmo módulo de nível inferior. Por exemplo: você pode construir várias visualizações para o mesmo aplicativo, vários aplicativos podem usar o mesmo serviço, vários serviços podem usar o mesmo gateway. O truque aqui é que, ao passar pelas camadas, o nível de funcionalidade muda. Módulos em camadas mais específicas não mapeiam 1-1 nos módulos da camada mais geral, porque os níveis de funcionalidade que eles expressam não mapeiam 1-1.
Quando você usa a abordagem vertical para design de embalagem, isto é, você divide primeiro por funcionalidade, então você força todos os blocos de construção com diferentes níveis de funcionalidade na mesma 'capa de funcionalidade'. Você pode projetar seus módulos gerais para um mais específico. Mas isso viola o princípio importante de que a camada mais geral não deve saber sobre camadas mais específicas. A camada de serviço, por exemplo, não deve ser modelada a partir de conceitos da camada de aplicativo.
fonte
Eu me pego aderindo aos princípios de design de embalagem do Tio Bob . Resumindo, as classes que devem ser reutilizadas e alteradas juntas (pela mesma razão, por exemplo, uma mudança de dependência ou uma mudança de framework) devem ser colocadas no mesmo pacote. IMO, a divisão funcional teria melhor chance de atingir esses objetivos do que a divisão vertical / específica do negócio na maioria das aplicações.
Por exemplo, uma fatia horizontal de objetos de domínio pode ser reutilizada por diferentes tipos de front-ends ou mesmo aplicativos e uma fatia horizontal do front-end da web provavelmente mudará junto quando a estrutura da web subjacente precisar ser alterada. Por outro lado, é fácil imaginar o efeito cascata dessas mudanças em muitos pacotes se as classes em diferentes áreas funcionais forem agrupadas nesses pacotes.
Obviamente, nem todos os tipos de software são iguais e a divisão vertical pode fazer sentido (em termos de atingir as metas de reutilização e capacidade de fechamento para alteração) em certos projetos.
fonte
Geralmente, há ambos os níveis de divisão presentes. Do topo, existem unidades de implantação. Eles são nomeados 'logicamente' (em seus termos, pense nos recursos do Eclipse). Dentro da unidade de implantação, você tem divisão funcional de pacotes (pense em plug-ins do Eclipse).
Por exemplo, feature is
com.feature
, e consiste emcom.feature.client
,com.feature.core
ecom.feature.ui
plugins. Dentro de plugins, tenho muito pouca divisão para outros pacotes, embora isso também não seja incomum.Atualização: Btw, há uma grande conversa de Juergen Hoeller sobre a organização de código na InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen é um dos arquitetos da Spring e sabe muito sobre essas coisas.
fonte
A maioria dos projetos java em que trabalhei corta os pacotes java funcionalmente primeiro, depois logicamente.
Normalmente as partes são grandes o suficiente para serem divididas em artefatos de construção separados, onde você pode colocar a funcionalidade central em um jar, apis em outro, coisas de front-end da web em um arquivo de guerra, etc.
fonte
Os pacotes devem ser compilados e distribuídos como uma unidade. Ao considerar quais classes pertencem a um pacote, um dos critérios principais são suas dependências. De quais outros pacotes (incluindo bibliotecas de terceiros) esta classe depende. Um sistema bem organizado agrupará classes com dependências semelhantes em um pacote. Isso limita o impacto de uma mudança em uma biblioteca, uma vez que apenas alguns pacotes bem definidos dependerão dela.
Parece que seu sistema lógico e vertical tende a "manchar" dependências na maioria dos pacotes. Ou seja, se cada recurso for empacotado como uma fatia vertical, cada pacote dependerá de cada biblioteca de terceiros que você usar. Qualquer mudança em uma biblioteca provavelmente afetará todo o sistema.
fonte
Eu pessoalmente prefiro agrupar classes logicamente, em seguida, dentro de que inclua um subpacote para cada participação funcional.
Objetivos de embalagem
Afinal, os pacotes tratam de agrupar coisas - a ideia de que classes relacionadas vivem próximas umas das outras. Se morarem no mesmo pacote, eles podem aproveitar a vantagem do pacote privado para limitar a visibilidade. O problema é que agrupar todas as suas coisas de visão e persistência em um pacote pode levar a muitas classes sendo misturadas em um único pacote. A próxima coisa sensata a fazer é criar visão, persistência, subpacotes util e classes de refatoração de acordo. O escopo Under infelizmente protegido e o pacote privado não suportam o conceito do pacote e subpacote atuais, pois isso ajudaria na aplicação de tais regras de visibilidade.
Vejo agora o valor na separação por meio da funcionalidade, porque qual valor existe para agrupar todas as coisas relacionadas à visão. As coisas nesta estratégia de nomenclatura se desconectam de algumas classes na visualização, enquanto outras estão em persistência e assim por diante.
Um exemplo da minha estrutura de embalagem lógica
Para fins de ilustração, vamos nomear dois módulos - usarei o módulo de nome como um conceito que agrupa classes em um ramo específico de uma árvore de pacote.
apple.model apple.store banana.model banana.storeVantagens
Um cliente que usa Banana.store.BananaStore é exposto apenas à funcionalidade que desejamos disponibilizar. A versão de hibernação é um detalhe de implementação que eles não precisam estar cientes e nem devem ver essas classes, pois elas adicionam confusão às operações de armazenamento.
Outras vantagens lógicas v funcionais
Quanto mais em direção à raiz, mais amplo se torna o escopo e as coisas pertencentes a um pacote começam a exibir mais e mais dependências das coisas pertencentes aos outros módulos. Se alguém fosse examinar, por exemplo, o módulo "banana", a maioria das dependências seriam limitadas a esse módulo. Na verdade, a maioria dos ajudantes em "banana" não seriam referenciados fora deste escopo do pacote.
Por que funcionalidade?
Que valor alguém alcança ao agrupar coisas com base na funcionalidade. A maioria das classes em tal caso são independentes umas das outras com pouca ou nenhuma necessidade de tirar vantagem dos métodos ou classes privadas do pacote. Refatorá-los em seus próprios subpacotes ganha pouco, mas ajuda a reduzir a desordem.
Alterações do desenvolvedor no sistema
Quando os desenvolvedores são incumbidos de fazer mudanças um pouco mais do que triviais, parece bobo que potencialmente eles têm mudanças que incluem arquivos de todas as áreas da árvore do pacote. Com a abordagem de estrutura lógica, suas mudanças são mais locais dentro da mesma parte da árvore do pacote, o que parece correto.
fonte
Ambas as abordagens funcional (arquitetônica) e lógica (recurso) para embalagem têm seu lugar. Muitos aplicativos de exemplo (aqueles encontrados em livros de texto, etc.) seguem a abordagem funcional de colocar apresentação, serviços de negócios, mapeamento de dados e outras camadas de arquitetura em pacotes separados. Em aplicativos de exemplo, cada pacote geralmente tem apenas algumas ou apenas uma classe.
Essa abordagem inicial é adequada, pois um exemplo inventado muitas vezes serve para: 1) mapear conceitualmente a arquitetura da estrutura que está sendo apresentada, 2) é feito com um único propósito lógico (por exemplo, adicionar / remover / atualizar / excluir animais de estimação de uma clínica) . O problema é que muitos leitores consideram isso um padrão sem limites.
Conforme um aplicativo de "negócio" se expande para incluir mais e mais recursos, seguir a abordagem funcional torna-se um fardo. Embora eu saiba onde procurar por tipos baseados na camada de arquitetura (por exemplo, controladores da web em um pacote "web" ou "ui", etc.), o desenvolvimento de um único recurso lógico começa a exigir a alternância entre muitos pacotes. Isso é complicado, no mínimo, mas é pior do que isso.
Como os tipos relacionados logicamente não são empacotados juntos, a API é amplamente divulgada; a interação entre os tipos relacionados logicamente é forçada a ser 'pública' para que os tipos possam importar e interagir uns com os outros (a capacidade de minimizar a visibilidade padrão / pacote é perdida).
Se eu estou construindo uma biblioteca de framework, por suposto meus pacotes seguirão uma abordagem de empacotamento funcional / arquitetônico. Meus consumidores de API podem até mesmo apreciar que suas instruções de importação contêm um pacote intuitivo com o nome da arquitetura.
Por outro lado, ao construir um aplicativo de negócios, empacotarei por recurso. Não tenho nenhum problema em colocar Widget, WidgetService e WidgetController no mesmo pacote " com.myorg.widget. " E, em seguida, tirar vantagem da visibilidade padrão (e ter menos instruções de importação, bem como dependências entre pacotes).
Existem, no entanto, casos cruzados. Se meu WidgetService for usado por muitos domínios lógicos (recursos), posso criar um pacote " com.myorg.common.service. " Também existe uma boa chance de eu criar classes com a intenção de serem reutilizáveis em todos os recursos e acabar com pacotes como " com.myorg.common.ui.helpers. " E " com.myorg.common.util. ". Posso até acabar movendo todas essas classes "comuns" posteriores em um projeto separado e incluí-las em meu aplicativo de negócios como uma dependência myorg-commons.jar.
fonte
Depende da granularidade de seus processos lógicos?
Se forem autônomos, geralmente você tem um novo projeto para eles no controle de origem, em vez de um novo pacote.
O projeto em que estou no momento está errando em direção à divisão lógica, há um pacote para o aspecto jython, um pacote para um mecanismo de regras, pacotes para foo, bar, binglewozzle, etc. Estou procurando ter os analisadores específicos de XML / Writeers para cada módulo dentro desse pacote, em vez de ter um pacote XML (o que eu fiz anteriormente), embora ainda haja um pacote XML principal onde vai a lógica compartilhada. Uma razão para isso, entretanto, é que ele pode ser extensível (plug-ins) e, portanto, cada plug-in também precisará definir seu código XML (ou banco de dados, etc.), portanto, centralizar isso pode introduzir problemas mais tarde.
No final, parece ser o que parece mais adequado para o projeto específico. No entanto, acho que é fácil empacotar ao longo das linhas do diagrama em camadas de projeto típico. Você acabará com uma mistura de embalagens lógicas e funcionais.
O que é necessário são namespaces marcados. Um analisador XML para alguma funcionalidade Jython pode ser marcado como Jython e XML, em vez de ter que escolher um ou outro.
Ou talvez eu esteja wibbling.
fonte
Tento projetar estruturas de pacote de forma que, se eu fosse desenhar um gráfico de dependência, seria fácil seguir e usar um padrão consistente, com o mínimo de referências circulares possíveis.
Para mim, isso é muito mais fácil de manter e visualizar em um sistema de nomenclatura vertical em vez de horizontal. se component1.display tiver uma referência a component2.dataaccess, isso emite mais avisos do que se display.component1 tiver uma referência a dataaccess. componente2.
Obviamente, os componentes compartilhados por ambos vêm em seus próprios pacotes.
fonte
Eu sigo totalmente e proponho a organização lógica ("por característica")! Um pacote deve seguir o conceito de "módulo" o mais próximo possível. A organização funcional pode espalhar um módulo por um projeto, resultando em menos encapsulamento e sujeito a mudanças nos detalhes de implementação.
Vamos pegar um plugin do Eclipse, por exemplo: colocar todas as visualizações ou ações em um pacote seria uma bagunça. Em vez disso, cada componente de um recurso deve ir para o pacote do recurso ou, se houver muitos, para subpacotes (featureA.handlers, featureA.preferences etc.)
Claro, o problema está no sistema de pacotes hierárquicos (que entre outros Java tem), o que torna o tratamento de questões ortogonais impossível ou pelo menos muito difícil - embora eles ocorram em todos os lugares!
fonte
Eu pessoalmente escolheria uma nomenclatura funcional. O motivo curto: evita a duplicação de código ou pesadelo de dependência.
Deixe-me elaborar um pouco. O que acontece quando você está usando um arquivo jar externo, com sua própria árvore de pacotes? Você está efetivamente importando o código (compilado) para o seu projeto e, com ele, uma árvore de pacotes (funcionalmente separada). Faria sentido usar as duas convenções de nomenclatura ao mesmo tempo? Não, a menos que isso estivesse escondido de você. E é, se o seu projeto for pequeno o suficiente e tiver um único componente. Mas se você tiver várias unidades lógicas, provavelmente não deseja reimplementar, digamos, o módulo de carregamento de arquivo de dados. Você deseja compartilhá-lo entre unidades lógicas, não ter dependências artificiais entre unidades logicamente não relacionadas e não precisa escolher em qual unidade colocar essa ferramenta compartilhada em particular.
Eu acho que é por isso que a nomenclatura funcional é mais usada em projetos que atingem, ou pretendem atingir, um certo tamanho, e a nomenclatura lógica é usada nas convenções de nomenclatura de classe para manter o controle da função específica, se alguma de cada classe em um pacote.
Tentarei responder com mais precisão a cada um de seus pontos sobre a nomenclatura lógica.
Se você tem que ir pescar em classes antigas para modificar funcionalidades quando tem uma mudança de planos, é um sinal de má abstração: você deve construir classes que forneçam uma funcionalidade bem definida, definível em uma frase curta. Apenas algumas classes de nível superior devem reunir tudo isso para refletir sua inteligência de negócios. Dessa forma, você poderá reutilizar mais código, ter uma manutenção mais fácil, documentação mais clara e menos problemas de dependência.
Isso depende principalmente da maneira como você groove seu projeto. Definitivamente, a visão lógica e funcional são ortogonais. Portanto, se você usar uma convenção de nomenclatura, precisará aplicar a outra aos nomes de classe para manter alguma ordem, ou bifurcar de uma convenção de nomenclatura para outra em alguma profundidade.
Os modificadores de acesso são uma boa maneira de permitir que outras classes que entendem seu processamento acessem as entranhas de sua classe. O relacionamento lógico não significa uma compreensão das restrições algorítmicas ou de simultaneidade. Funcional pode, embora não o faça. Estou muito cansado de modificadores de acesso que não sejam públicos e privados, porque muitas vezes eles escondem a falta de arquitetura adequada e abstração de classes.
Em grandes projetos comerciais, a mudança de tecnologias acontece com mais frequência do que você imagina. Por exemplo, já tive que mudar 3 vezes do analisador XML, 2 vezes da tecnologia de cache e 2 vezes do software de geolocalização. Ainda bem que escondi todos os detalhes difíceis em um pacote dedicado ...
fonte
utils
pacote. Então, todas as classes que precisarem podem simplesmente depender desse pacote.É uma experiência interessante não usar pacotes (exceto para o pacote raiz).
A questão que surge então é quando e por que faz sentido introduzir pacotes. Presumivelmente, a resposta será diferente do que você teria respondido no início do projeto.
Presumo que a sua pergunta surja, porque os pacotes são como categorias e às vezes é difícil decidir por uma ou outra. Às vezes, as tags seriam mais úteis para comunicar que uma classe pode ser usada em muitos contextos.
fonte
Do ponto de vista puramente prático, construções de visibilidade do Java permitem classes no mesmo pacote para métodos de acesso e propriedades com
protected
edefault
visibilidade, bem como ospublic
queridos. Usar métodos não públicos de uma camada completamente diferente do código seria definitivamente um cheiro forte de código. Portanto, tendo a colocar classes da mesma camada no mesmo pacote.Não costumo usar esses métodos protegidos ou padrão em outros lugares - exceto possivelmente nos testes de unidade para a classe - mas quando faço isso, é sempre de uma classe na mesma camada
fonte
Depende. Na minha linha de trabalho, às vezes dividimos pacotes por funções (acesso a dados, análises) ou por classe de ativos (crédito, ações, taxas de juros). Basta selecionar a estrutura mais conveniente para sua equipe.
fonte
Da minha experiência, a reutilização cria mais problemas do que solução. Com os processadores e memória mais recentes e baratos, eu preferiria a duplicação de código em vez de uma integração rígida para reutilizar.
fonte