Pensé en esto hace un tiempo y recientemente resurgió cuando mi tienda está haciendo su primera aplicación web Java real.
Como introducción, veo dos estrategias principales de nomenclatura de paquetes. (Para ser claros, no me refiero a toda la parte 'domain.company.project' de esto, me refiero a la convención de paquetes debajo de eso). De todos modos, las convenciones de nomenclatura de paquetes que veo son las siguientes:
Funcional: Nombrar sus paquetes según su función arquitectónica en lugar de su identidad según el dominio comercial. Otro término para esto podría ser nombrar según 'capa'. Entonces, tendrías un paquete * .ui y un paquete * .domain y un paquete * .orm. Sus paquetes son cortes horizontales en lugar de verticales.
Esto es mucho más común que la denominación lógica. De hecho, no creo haber visto ni oído hablar de un proyecto que haga esto. Por supuesto, esto me hace receloso (algo así como pensar que has encontrado una solución a un problema de NP), ya que no soy muy inteligente y supongo que todos deben tener buenas razones para hacerlo de la forma en que lo hacen. Por otro lado, no me opongo a que la gente simplemente extrañe al elefante en la habitación y nunca escuché un argumento real para nombrar paquetes de esta manera. Simplemente parece ser el estándar de facto.
Lógico: Nombrar sus paquetes de acuerdo con su identidad de dominio comercial y poner cada clase que tiene que ver con esa porción vertical de funcionalidad en ese paquete.
Nunca había visto ni oído hablar de esto, como mencioné antes, pero tiene mucho sentido para mí.
Tiendo a acercarme a los sistemas verticalmente en lugar de horizontalmente. Quiero entrar y desarrollar el sistema de procesamiento de pedidos, no la capa de acceso a datos. Obviamente, hay una buena posibilidad de que toque la capa de acceso a datos en el desarrollo de ese sistema, pero el punto es que no lo veo de esa manera. Lo que esto significa, por supuesto, es que cuando recibo una orden de cambio o quiero implementar alguna característica nueva, sería bueno no tener que andar buscando en un montón de paquetes para encontrar todas las clases relacionadas. En cambio, solo miro en el paquete X porque lo que estoy haciendo tiene que ver con X.
Desde el punto de vista del desarrollo, veo como una gran ventaja que sus paquetes documenten el dominio de su negocio en lugar de su arquitectura. Siento que el dominio es casi siempre la parte del sistema que es más difícil de asimilar, ya que la arquitectura del sistema, especialmente en este punto, se está volviendo casi mundana en su implementación. El hecho de que pueda llegar a un sistema con este tipo de convención de nomenclatura e instantáneamente desde la nomenclatura de los paquetes saber que se trata de pedidos, clientes, empresas, productos, etc., parece bastante útil.
Parece que esto le permitiría aprovechar mucho mejor los modificadores de acceso de Java. Esto le permite definir interfaces de manera mucho más limpia en subsistemas que en capas del sistema. Entonces, si tiene un subsistema de pedidos que desea que sea persistente de manera transparente, en teoría, nunca podría dejar que nadie sepa que es persistente al no tener que crear interfaces públicas para sus clases de persistencia en la capa dao y, en su lugar, empaquetar la clase dao en sólo con las clases de las que trata. Obviamente, si desea exponer esta funcionalidad, puede proporcionar una interfaz para ella o hacerla pública. Parece que pierde mucho de esto al dividir una porción vertical de las funciones de su sistema en varios paquetes.
Supongo que una desventaja que puedo ver es que hace que arrancar capas sea un poco más difícil. En lugar de simplemente eliminar o cambiar el nombre de un paquete y luego colocar uno nuevo en su lugar con una tecnología alternativa, debe ingresar y cambiar todas las clases en todos los paquetes. Sin embargo, no veo que esto sea gran cosa. Puede ser por falta de experiencia, pero tengo que imaginar que la cantidad de veces que intercambia tecnologías palidece en comparación con la cantidad de veces que ingresa y edita segmentos de características verticales dentro de su sistema.
Entonces, supongo que la pregunta se le haría a usted, ¿cómo nombran sus paquetes y por qué? Por favor, comprenda que no creo necesariamente que me haya topado con la gallina de los huevos de oro o algo así. Soy bastante nuevo en todo esto y tengo mayormente experiencia académica. Sin embargo, no puedo detectar los agujeros en mi razonamiento, así que espero que todos puedan para que yo pueda seguir adelante.
fuente
Respuestas:
Para el diseño de paquetes, primero lo divido por capa, luego por alguna otra funcionalidad.
Hay algunas reglas adicionales:
Entonces, para una aplicación web, por ejemplo, podría tener las siguientes capas en su nivel de aplicación (de arriba a abajo):
Para el diseño del paquete resultante, estas son algunas reglas adicionales:
<prefix.company>.<appname>.<layer>
<root>.<logic>
<root>.private
Aquí hay un diseño de ejemplo.
La capa de presentación está dividida por tecnología de vista y, opcionalmente, por (grupos de) aplicaciones.
La capa de aplicación se divide en casos de uso.
La capa de servicio se divide en dominios comerciales, influenciada por la lógica del dominio en un nivel de backend.
La capa de integración se divide en 'tecnologías' y objetos de acceso.
Las ventajas de separar paquetes como este es que es más fácil administrar la complejidad y aumenta la capacidad de prueba y la reutilización. Si bien parece una gran cantidad de gastos generales, en mi experiencia, en realidad es muy natural y todos los que trabajan en esta estructura (o similar) lo captan en cuestión de días.
¿Por qué creo que el enfoque vertical no es tan bueno?
En el modelo en capas, varios módulos de alto nivel diferentes pueden utilizar el mismo módulo de nivel inferior. Por ejemplo: puede crear varias vistas para la misma aplicación, varias aplicaciones pueden utilizar el mismo servicio, varios servicios pueden utilizar la misma puerta de enlace. El truco aquí es que al moverse a través de las capas, el nivel de funcionalidad cambia. Los módulos en capas más específicas no se asignan 1-1 en módulos de la capa más general, porque los niveles de funcionalidad que expresan no se asignan 1-1.
Cuando utiliza el enfoque vertical para el diseño de paquetes, es decir, primero divide por funcionalidad, luego fuerza todos los bloques de construcción con diferentes niveles de funcionalidad en la misma "chaqueta de funcionalidad". Puede diseñar sus módulos generales para uno más específico. Pero esto viola el importante principio de que la capa más general no debería conocer las capas más específicas. La capa de servicio, por ejemplo, no debe modelarse a partir de conceptos de la capa de aplicación.
fuente
Me apego a los principios de diseño de paquetes del tío Bob . En resumen, las clases que deben reutilizarse juntas y cambiarse juntas (por la misma razón, por ejemplo, un cambio de dependencia o un cambio de marco) deben colocarse en el mismo paquete. En mi opinión, el desglose funcional tendría más posibilidades de lograr estos objetivos que el desglose vertical / específico de la empresa en la mayoría de las aplicaciones.
Por ejemplo, una porción horizontal de objetos de dominio puede ser reutilizada por diferentes tipos de front-end o incluso aplicaciones, y es probable que una porción horizontal del front-end web cambie a la vez cuando sea necesario cambiar el marco web subyacente. Por otro lado, es fácil imaginar el efecto dominó de estos cambios en muchos paquetes si las clases de diferentes áreas funcionales se agrupan en esos paquetes.
Obviamente, no todos los tipos de software son iguales y el desglose vertical puede tener sentido (en términos de lograr los objetivos de reutilización y cierre de cambios) en ciertos proyectos.
fuente
Suelen estar presentes ambos niveles de división. Desde arriba, hay unidades de despliegue. Estos se nombran 'lógicamente' (en sus términos, piense en las características de Eclipse). Dentro de la unidad de implementación, tiene una división funcional de paquetes (piense en los complementos de Eclipse).
Por ejemplo, la característica es
com.feature
, y consta decom.feature.client
,com.feature.core
ycom.feature.ui
complementos. Dentro de los complementos, tengo muy poca división con otros paquetes, aunque eso tampoco es inusual.Actualización: Por cierto, hay una gran charla de Juergen Hoeller sobre la organización del código en InfoQ: http://www.infoq.com/presentations/code-organization-large-projects . Juergen es uno de los arquitectos de Spring y sabe mucho sobre este tema.
fuente
La mayoría de los proyectos de Java en los que he trabajado cortan los paquetes de Java funcionalmente primero, luego lógicamente.
Por lo general, las partes son lo suficientemente grandes como para dividirlas en artefactos de compilación separados, donde puede colocar la funcionalidad principal en un jar, las apis en otro, las cosas de la interfaz web en un archivo de guerra, etc.
fuente
Los paquetes deben compilarse y distribuirse como una unidad. Al considerar qué clases pertenecen a un paquete, uno de los criterios clave son sus dependencias. De qué otros paquetes (incluidas las bibliotecas de terceros) depende esta clase. Un sistema bien organizado agrupará clases con dependencias similares en un paquete. Esto limita el impacto de un cambio en una biblioteca, ya que solo unos pocos paquetes bien definidos dependerán de ella.
Parece que su sistema vertical y lógico podría tender a "manchar" las dependencias en la mayoría de los paquetes. Es decir, si cada característica está empaquetada como un segmento vertical, cada paquete dependerá de cada biblioteca de terceros que utilice. Es probable que cualquier cambio en una biblioteca se propague por todo el sistema.
fuente
Personalmente, prefiero agrupar las clases de manera lógica y luego incluir un subpaquete para cada participación funcional.
Objetivos del embalaje
Después de todo, los paquetes se tratan de agrupar cosas: la idea de que las clases relacionadas vivan cerca unas de otras. Si viven en el mismo paquete, pueden aprovechar el paquete privado para limitar la visibilidad. El problema es que agrupar todas las cosas de vista y persistencia en un paquete puede llevar a que muchas clases se mezclen en un solo paquete. Lo siguiente que se puede hacer es crear vista, persistencia, subpaquetes util y clases de refactorización en consecuencia. Desafortunadamente, el alcance privado del paquete y protegido no admite el concepto del paquete y subpaquete actual, ya que esto ayudaría a hacer cumplir dichas reglas de visibilidad.
Ahora veo valor en la separación a través de la funcionalidad porque ¿qué valor hay para agrupar todas las cosas relacionadas con la vista? Las cosas en esta estrategia de nomenclatura se desconectan con algunas clases en la vista mientras que otras permanecen en persistencia y así sucesivamente.
Un ejemplo de mi estructura lógica de empaque
Para fines de ilustración, nombremos dos módulos; usaré el módulo de nombres como un concepto que agrupa clases bajo una rama particular de un árbol de paquetes.
apple.model apple.store banana.model banana.storeVentajas
Un cliente que utiliza Banana.store.BananaStore solo está expuesto a la funcionalidad que deseamos poner a disposición. La versión de hibernación es un detalle de implementación que no necesitan conocer ni deben ver estas clases, ya que agregan desorden a las operaciones de almacenamiento.
Otras ventajas lógicas v funcionales
Cuanto más hacia la raíz, más amplio se vuelve el alcance y las cosas que pertenecen a un paquete comienzan a exhibir más y más dependencias de las cosas que pertenecen a otros módulos. Si uno examinara, por ejemplo, el módulo "banana", la mayoría de las dependencias se limitarían a ese módulo. De hecho, la mayoría de los ayudantes bajo "banana" no serían referenciados en absoluto fuera del alcance de este paquete.
¿Por qué funcionalidad?
¿Qué valor se logra al agrupar las cosas en función de la funcionalidad? La mayoría de las clases en tal caso son independientes entre sí con poca o ninguna necesidad de aprovechar los métodos o clases privados del paquete. Refactorizarlos en sus propios subpaquetes gana poco, pero ayuda a reducir el desorden.
Desarrollador cambia al sistema
Cuando los desarrolladores tienen la tarea de realizar cambios que son un poco más que triviales, parece una tontería que potencialmente tengan cambios que incluyan archivos de todas las áreas del árbol de paquetes. Con el enfoque estructurado lógico, sus cambios son más locales dentro de la misma parte del árbol de paquetes, lo que parece correcto.
fuente
Ambos enfoques funcionales (arquitectónicos) y lógicos (características) del empaque tienen un lugar. Muchas aplicaciones de ejemplo (las que se encuentran en libros de texto, etc.) siguen el enfoque funcional de colocar presentaciones, servicios comerciales, mapeo de datos y otras capas arquitectónicas en paquetes separados. En aplicaciones de ejemplo, cada paquete a menudo tiene solo unas pocas o solo una clase.
Este enfoque inicial está bien, ya que un ejemplo artificial a menudo sirve para: 1) trazar un mapa conceptual de la arquitectura del marco que se presenta, 2) se hace con un único propósito lógico (por ejemplo, agregar / eliminar / actualizar / eliminar mascotas de una clínica) . El problema es que muchos lectores toman esto como un estándar que no tiene límites.
A medida que una aplicación "empresarial" se expande para incluir más y más funciones, seguir el enfoque funcional se convierte en una carga. Aunque sé dónde buscar tipos basados en la capa de arquitectura (por ejemplo, controladores web en un paquete "web" o "ui", etc.), el desarrollo de una única característica lógica comienza a requerir saltar de un lado a otro entre muchos paquetes. Esto es engorroso, como mínimo, pero es peor que eso.
Dado que los tipos relacionados lógicamente no se empaquetan juntos, la API se publicita demasiado; la interacción entre tipos relacionados lógicamente se ve obligada a ser "pública" para que los tipos puedan importar e interactuar entre sí (se pierde la capacidad de minimizar a la visibilidad predeterminada / paquete).
Si estoy construyendo una biblioteca marco, por supuesto, mis paquetes seguirán un enfoque de empaquetado funcional / arquitectónico. Mis consumidores de API podrían incluso apreciar que sus declaraciones de importación contienen un paquete intuitivo con el nombre de la arquitectura.
Por el contrario, al crear una aplicación empresarial, la empaquetaré por función. No tengo ningún problema para colocar Widget, WidgetService y WidgetController en el mismo paquete " com.myorg.widget. " Y luego aprovechar la visibilidad predeterminada (y tener menos declaraciones de importación así como dependencias entre paquetes).
Sin embargo, existen casos cruzados. Si mi WidgetService es utilizado por muchos dominios lógicos (características), podría crear un paquete " com.myorg.common.service. " También hay una buena posibilidad de que cree clases con la intención de que sean reutilizables en todas las funciones y termine con paquetes como " com.myorg.common.ui.helpers. " Y " com.myorg.common.util. ". Incluso puedo terminar moviendo todas estas clases "comunes" posteriores en un proyecto separado e incluirlas en mi aplicación comercial como una dependencia myorg-commons.jar.
fuente
¿Depende de la granularidad de sus procesos lógicos?
Si son independientes, a menudo tiene un nuevo proyecto para ellos en control de código fuente, en lugar de un nuevo paquete.
El proyecto en el que estoy en este momento se está equivocando hacia la división lógica, hay un paquete para el aspecto jython, un paquete para un motor de reglas, paquetes para foo, bar, binglewozzle, etc. Estoy buscando tener analizadores XML específicos / escritores para cada módulo dentro de ese paquete, en lugar de tener un paquete XML (lo que he hecho anteriormente), aunque todavía habrá un paquete XML central donde va la lógica compartida. Sin embargo, una razón para esto es que puede ser extensible (complementos) y, por lo tanto, cada complemento necesitará definir también su código XML (o base de datos, etc.), por lo que centralizarlo podría presentar problemas más adelante.
Al final, parece ser lo que parece más sensato para el proyecto en particular. Sin embargo, creo que es fácil de empaquetar siguiendo las líneas del diagrama en capas de un proyecto típico. Terminará con una combinación de empaque lógico y funcional.
Lo que se necesita son espacios de nombres etiquetados. Un analizador XML para algunas funciones de Jython podría etiquetarse tanto como Jython como XML, en lugar de tener que elegir uno u otro.
O tal vez estoy temblando.
fuente
Intento diseñar estructuras de paquetes de tal manera que si tuviera que dibujar un gráfico de dependencia, sería fácil de seguir y usar un patrón consistente, con la menor cantidad posible de referencias circulares.
Para mí, esto es mucho más fácil de mantener y visualizar en un sistema de nombres vertical en lugar de horizontal. si component1.display tiene una referencia a component2.dataaccess, eso lanza más campanas de advertencia que si display.component1 tiene una referencia a dataaccess. componente2.
Por supuesto, los componentes compartidos por ambos van en su propio paquete.
fuente
¡Sigo y propongo totalmente la organización lógica ("por función")! Un paquete debe seguir el concepto de "módulo" lo más fielmente posible. La organización funcional puede extender un módulo sobre un proyecto, lo que resulta en menos encapsulación y es propensa a cambios en los detalles de implementación.
Tomemos un complemento de Eclipse, por ejemplo: poner todas las vistas o acciones en un paquete sería un desastre. En cambio, cada componente de una característica debe ir al paquete de la característica, o si hay muchos, en subpaquetes (featureA.handlers, featureA.preferences, etc.)
Por supuesto, el problema radica en el sistema de paquetes jerárquicos (que, entre otros, tiene Java), que hace que el manejo de preocupaciones ortogonales sea imposible o al menos muy difícil, ¡aunque ocurren en todas partes!
fuente
Personalmente, optaría por nombres funcionales. La razón corta: evita la duplicación de código o la pesadilla de dependencia.
Déjame desarrollar un poco. ¿Qué sucede cuando está utilizando un archivo jar externo, con su propio árbol de paquetes? Está importando efectivamente el código (compilado) a su proyecto, y con él un árbol de paquetes (separado funcionalmente). ¿Tendría sentido utilizar las dos convenciones de nomenclatura al mismo tiempo? No, a menos que se lo oculte. Y lo es, si su proyecto es lo suficientemente pequeño y tiene un solo componente. Pero si tiene varias unidades lógicas, probablemente no desee volver a implementar, digamos, el módulo de carga de archivos de datos. Desea compartirlo entre unidades lógicas, no tener dependencias artificiales entre unidades lógicamente no relacionadas y no tener que elegir en qué unidad va a colocar esa herramienta compartida en particular.
Supongo que esta es la razón por la que la denominación funcional es la más utilizada en proyectos que alcanzan, o están destinados a alcanzar, un cierto tamaño, y la denominación lógica se utiliza en las convenciones de denominación de clases para realizar un seguimiento del rol específico, si es que hay alguno de cada clase en un paquete.
Intentaré responder con mayor precisión a cada uno de sus puntos sobre la denominación lógica.
Si tienes que ir a pescar en clases antiguas para modificar funcionalidades cuando tienes un cambio de planes, es señal de mala abstracción: debes construir clases que brinden una funcionalidad bien definida, definible en una frase corta. Solo unas pocas clases de alto nivel deberían reunir todo esto para reflejar su inteligencia empresarial. De esta manera, podrá reutilizar más código, tener un mantenimiento más fácil, una documentación más clara y menos problemas de dependencia.
Eso depende principalmente de la forma en que asimile su proyecto. Definitivamente, la vista lógica y funcional es ortogonal. Entonces, si usa una convención de nomenclatura, debe aplicar la otra a los nombres de las clases para mantener algún orden, o pasar de una convención de nomenclatura a otra con cierta profundidad.
Los modificadores de acceso son una buena forma de permitir que otras clases comprendan su procesamiento accedan a las entrañas de su clase. La relación lógica no significa una comprensión de las restricciones algorítmicas o de concurrencia. Funcional puede, aunque no lo hace. Estoy muy cansado de los modificadores de acceso que no sean públicos y privados, porque a menudo ocultan una falta de una arquitectura adecuada y una abstracción de clases.
En grandes proyectos comerciales, el cambio de tecnologías ocurre con más frecuencia de lo que cree. Por ejemplo, ya he tenido que cambiar 3 veces el analizador XML, 2 veces la tecnología de almacenamiento en caché y 2 veces el software de geolocalización. Menos mal que había escondido todos los detalles en un paquete dedicado ...
fuente
utils
paquete. Entonces, cualquier clase que lo necesite simplemente puede depender de ese paquete.Es un experimento interesante no usar paquetes en absoluto (excepto el paquete raíz).
La pregunta que surge entonces es cuándo y por qué tiene sentido introducir paquetes. Presumiblemente, la respuesta será diferente a la que habría respondido al comienzo del proyecto.
Supongo que su pregunta surge en absoluto, porque los paquetes son como categorías y, a veces, es difícil decidir por uno u otro. A veces, las etiquetas serían más apreciadas para comunicar que una clase se puede usar en muchos contextos.
fuente
Desde un punto de vista puramente práctico, construcciones de visibilidad de Java permiten a las clases en el mismo paquete a métodos de acceso y con propiedades
protected
ydefault
visibilidad, así como lapublic
queridos. Usar métodos no públicos de una capa completamente diferente del código definitivamente sería un gran olor a código. Por eso tiendo a poner clases de la misma capa en el mismo paquete.No suelo usar estos métodos protegidos o predeterminados en otros lugares, excepto posiblemente en las pruebas unitarias para la clase, pero cuando lo hago, siempre es de una clase en la misma capa
fuente
Depende. En mi línea de trabajo, a veces dividimos los paquetes por funciones (acceso a datos, análisis) o por clase de activos (crédito, acciones, tipos de interés). Simplemente seleccione la estructura que sea más conveniente para su equipo.
fuente
Desde mi experiencia, la reutilización crea más problemas que la solución. Con los procesadores y la memoria más recientes y baratos, preferiría la duplicación de código en lugar de integrarlo estrechamente para reutilizarlo.
fuente