Estructuración de proyectos y dependencias de grandes aplicaciones de winforms en C #

ACTUALIZAR:
Esta es una de mis preguntas más visitadas, y todavía no he encontrado una solución satisfactoria para mi proyecto. Una idea que leí en una respuesta a otra pregunta es crear una herramienta que pueda construir soluciones ‘sobre la marcha’ para proyectos que elijas de una lista. Todavía tengo que intentarlo.


¿Cómo se estructura una aplicación muy grande?

  • ¿Múltiples proyectos / ensamblajes pequeños en una gran solución?
  • ¿Algunos grandes proyectos?
  • Una solución por proyecto?

¿Y cómo gestionas las dependencias en el caso de que no tengas una solución? Nota: estoy buscando consejos basados ​​en la experiencia, no las respuestas que encontraste en Google (puedo hacerlo yo mismo).

Actualmente estoy trabajando en una aplicación que tiene más de 80 dlls, cada uno en su propia solución. Administrar las dependencias es casi un trabajo de tiempo completo. Existe un “control de fuente” interno personalizado con funcionalidad adicional para copiar dlls de dependencia en cualquier lugar. Parece una solución subóptima para mí, pero ¿hay una mejor manera? Trabajar en una solución con 80 proyectos sería bastante difícil en la práctica, me temo.

(Contexto: winforms, no web)

EDITAR: (Si crees que esta es una pregunta diferente, déjame un comentario)

Me parece que hay interdependencias entre:

  • Estructura del proyecto / solución para una aplicación
  • Estructura de Carpeta / Archivo
  • Estructura de twig para control de fuente (si usa ramificación)

Pero tengo grandes dificultades para separarlos y considerarlos individualmente, si eso es posible.

He hecho otra pregunta relacionada aquí .

Fuente de control

Tenemos 20 o 30 proyectos integrados en 4 o 5 soluciones discretas. Estamos utilizando Subversion para SCM.

1) Tenemos un árbol en SVN que contiene todos los proyectos organizados lógicamente por nombre de espacio y nombre del proyecto. Hay un .sln en la raíz que los construirá a todos, pero eso no es un requisito.

2) Para cada solución real tenemos una nueva carpeta troncos en SVN con SVN: referencias externas a todos los proyectos requeridos para que se actualicen desde sus ubicaciones en el árbol principal.

3) En cada solución se encuentra el archivo .sln más algunos otros archivos requeridos, más cualquier código que sea exclusivo de esa solución y que no se comparta con otras soluciones.

Tener muchos proyectos más pequeños es un poco molesto a veces (por ejemplo, los mensajes de actualización de TortoiseSVN se vuelven complicados con todos esos enlaces externos) pero tiene la gran ventaja de que las dependencias no son circulares, por lo que nuestros proyectos de UI dependen de la BO proyectos pero los proyectos BO no pueden hacer referencia a la IU (¡y tampoco deberían!).

Arquitectura Hemos cambiado completamente al uso del patrón empresarial MS SCSF y CAB para administrar la forma en que nuestros diversos proyectos se combinan e interactúan en una interfaz de Win Forms. No estoy seguro de si tiene los mismos problemas (varios módulos necesitan compartir espacio en un entorno de formularios comunes), pero si lo hace, esto puede traer cierta cordura y convención a la forma de diseñar y montar sus soluciones.

Menciono eso porque SCSF tiende a fusionar las funciones de tipo BO y UI en el mismo módulo, mientras que anteriormente manteníamos una estricta política de 3 niveles:

FW – Código de marco. Código cuya función se relaciona con problemas de software. BO – Objetos de negocios. Código cuya función se relaciona con problemas de dominio del problema. UI – Código que se relaciona con la UI.

En ese escenario las dependencias son estrictamente UI -> BO -> FW

Hemos encontrado que podemos mantener esa estructura incluso mientras usamos módulos generados por SCSF para que todo sea bueno en el mundo 🙂

Para administrar dependencias, independientemente del número de ensamblados / espacios de nombres / proyectos que tenga, puede echar un vistazo a la herramienta NDepender .

Personalmente, fomento pocos proyectos grandes, dentro de una o varias soluciones si es necesario. Escribí sobre mis motivaciones para hacerlo aquí: Aprovechar los comstackdores C # y VB.NET perf

Creo que es muy importante que tenga una solución que contenga todos sus 80 proyectos, incluso si la mayoría de los desarrolladores usan otras soluciones la mayor parte del tiempo. En mi experiencia, tiendo a trabajar con una gran solución, pero para evitar el dolor de reconstruir todos los proyectos cada vez que presiono F5, voy a Solution Explorer, hago clic derecho en los proyectos que no me interesan en este momento, y haz “Descargar proyecto”. De esta forma, el proyecto permanece en la solución pero no me cuesta nada.

Habiendo dicho eso, 80 es un gran número. Dependiendo de qué tan bien esos 80 se dividan en subsistemas dicretes, también podría crear otros archivos de solución que contengan cada uno un subconjunto significativo. Eso me ahorraría el esfuerzo de muchas operaciones de clic derecho / descarga. Sin embargo, el hecho de que tenga una gran solución significa que siempre hay una visión definitiva de sus interdependencias.

En todos los sistemas de control de fuente con los que he trabajado, su integración VS elige poner el archivo .sln en control de fuente, y muchos no funcionan correctamente a menos que ese archivo .sln esté en control de fuente. Me resulta intrigante, ya que el archivo .sln solía considerarse una cosa personal, en lugar de un proyecto en general. Creo que el único tipo de archivo .sln que definitivamente merece el control de código fuente es la “única solución grande” que contiene todos los proyectos. Puede usarlo para comstackciones automatizadas, por ejemplo. Como dije, las personas pueden crear sus propias soluciones por conveniencia, y no estoy en contra de aquellas que están en control de la fuente, pero son más significativas para las personas que para el proyecto.

Creo que la mejor solución es dividirla en soluciones más pequeñas. En la empresa en la que trabajo actualmente, tenemos el mismo problema; 80 proyectos ++ en solución. Lo que hemos hecho, es dividir en varias soluciones más pequeñas con proyectos que pertenecen juntos. Los dll dependientes de otros proyectos se crean y se vinculan al proyecto y se registran en el sistema de control de origen junto con el proyecto. Utiliza más espacio en disco, pero el disco es barato. Al hacerlo de esta manera, podemos quedarnos con la versión 1 de un proyecto hasta que la actualización a la versión 1.5 sea absolutamente necesaria. Aún así, tienes el trabajo de agregar archivos DLL cuando decidas actualizar a otra versión de la dll. Hay un proyecto en el código de google llamado TreeFrog que muestra cómo estructurar la solución y el árbol de desarrollo. Todavía no contiene documentación sobre papilla, pero supongo que puede hacerse una idea de cómo hacerlo mirando la estructura.

Un método que he visto funcionar bien es tener una gran solución que contenga todos los proyectos, para permitir que se pruebe una construcción de proyecto (sin embargo, nadie usó esto para construir, ya que era demasiado grande), y luego tener proyectos más pequeños para desarrolladores que usaron varios proyectos relacionados agrupados.

Estos tenían dependencias de otros proyectos pero, a menos que las interfaces cambiaran, o necesitaran actualizar la versión de la DLL que estaban usando, podrían continuar usando los proyectos más pequeños sin preocuparse por todo lo demás. Por lo tanto, podían verificar proyectos mientras trabajaban en ellos, y luego fijarlos (después de cambiar el número de versión), cuando otros usuarios deberían comenzar a usarlos.

Finalmente, una o dos veces por semana o, incluso con más frecuencia, la solución completa se reconstruyó usando solo código fijado, lo que permitió comprobar si la integración funcionaba correctamente y proporcionar a los evaluadores una buena versión para evaluar.

A menudo descubrimos que grandes secciones de código no cambiaban con frecuencia, por lo que era inútil cargarlo todo el tiempo. (Cuando trabajas en proyectos pequeños).

Otra ventaja de utilizar este enfoque es que, en ciertos casos, tuvimos piezas de funcionalidad que tardaron meses en completarse, mediante el uso del enfoque anterior significaba que esto podría continuar sin interrumpir otros flujos de trabajo.

Supongo que un criterio clave para esto es no tener muchas dependencias cruzadas en todas sus soluciones, si lo hace, este enfoque podría no ser apropiado, pero si las dependencias son más limitadas, entonces este podría ser el camino a seguir.

Para un par de sistemas en los que he trabajado teníamos diferentes soluciones para diferentes componentes. Cada solución tenía una carpeta de salida común (con subcarpetas de depuración y liberación)

Usamos referencias de proyectos dentro de una solución y archivamos referencias entre ellos. Cada proyecto usó Rutas de referencia para ubicar los ensamblajes desde otras soluciones. Tuvimos que editar manualmente los archivos .csproj.user para agregar una variable msbuild $ (Configuración) a las rutas de referencia, ya que VS insiste en validar la ruta.

Para comstackciones fuera de VS, he escrito scripts de msbuild que identifican recursivamente las dependencias de los proyectos, los extrae de la subversión y los construye.

Renuncié a las referencias de proyectos (aunque tus macros suenan maravillosas) por las siguientes razones:

  • No fue fácil cambiar entre diferentes soluciones donde a veces existían proyectos de dependencia y otros no.
  • Necesario para poder abrir el proyecto por sí mismo y construirlo, y desplegarlo independientemente de otros proyectos. Si se crea con referencias de proyecto, esto a veces causaba problemas con la implementación, porque una referencia de proyecto hacía que buscara una versión específica o superior, o algo así. Limitaba la capacidad de mezclar y combinar para intercambiar diferentes versiones de dependencias.
  • Además, tenía proyectos que apuntan a diferentes versiones de .NET Framework, por lo que una verdadera referencia del proyecto no siempre ocurría de todos modos.

(Para su información, todo lo que hice fue para VB.NET, por lo que no estoy seguro si hay alguna diferencia sutil en el comportamiento de C #)

Asique:

  • Construyo contra cualquier proyecto que esté abierto en la solución, y aquellos que no lo son, desde una carpeta global, como C: \ GlobalAssemblies
  • Mi servidor de integración continua lo mantiene actualizado en una red compartida, y tengo un archivo por lotes para sincronizar cualquier elemento nuevo en mi carpeta local.
  • Tengo otra carpeta local como C: \ GlobalAssembliesDebug donde cada proyecto tiene un paso posterior a la comstackción que copia los contenidos de su carpeta bin a esta carpeta de depuración, solo cuando está en el modo DEPURAR.
  • Cada proyecto tiene estas dos carpetas globales agregadas a sus rutas de referencia. (Primero, C: \ GlobalAssembliesDebug, y luego C: \ GlobalAssemblies). Tengo que agregar manualmente estas rutas de referencia a los archivos .vbproj, porque la IU de Visual Studio las agrega al archivo .vbprojuser.
  • Tengo un paso previo a la comstackción que, si está en el modo RELEASE, elimina los contenidos de C: \ GlobalAssembliesDebug.
  • En cualquier proyecto que sea el proyecto anfitrión, si hay archivos no dlls que necesito copiar (archivos de texto enviados a las carpetas bin de otro proyecto que necesito), entonces pongo un paso previo a ese proyecto para copiarlos en el proyecto host.
  • Tengo que especificar manualmente las dependencias del proyecto en las propiedades de la solución, para que se construyan en el orden correcto.

Entonces, lo que hace es:

  • Me permite usar proyectos en cualquier solución sin meterme con referencias de proyectos.
  • Visual Studio todavía me permite acceder a los proyectos de dependencia que están abiertos en la solución.
  • En modo DEBUG, se construye contra proyectos abiertos. Entonces, primero mira hacia C: \ GlobalAssembliesDebug, luego si no está allí, hacia C: \ GlobalAssemblies
  • En modo RELEASE, ya que elimina todo de C: \ GlobalAssembliesDebug, solo se ve a C: \ GlobalAssemblies. La razón por la que deseo esto es para que las comstackciones publicadas no se construyan en función de nada que haya cambiado temporalmente en mi solución.
  • Es fácil cargar y descargar proyectos sin mucho esfuerzo.

Por supuesto, no es perfecto. La experiencia de depuración no es tan buena como una referencia de proyecto. (No se puede hacer cosas como “ir a definición” y hacer que funcione bien), y algunas otras cosas extrañas.

De todos modos, ahí es donde estoy en mi bash de hacer que las cosas funcionen lo mejor para nosotros.

Tenemos una solución gigantesca en el control de fuente, en la twig principal.

Pero, cada desarrollador / equipo que trabaja en la parte más pequeña del proyecto, tiene su propia sucursal que contiene una solución con solo unos pocos proyectos que son necesarios. De esa manera, esa solución es lo suficientemente pequeña como para ser fácilmente mantenida, y no influir en los otros proyectos / dlls en la solución más grande.

Sin embargo, hay una condición para esto: no debe haber demasiados proyectos interconectados dentro de la solución.

De acuerdo, habiendo digerido esta información, y también respondiendo a esta pregunta sobre referencias de proyectos, actualmente estoy trabajando con esta configuración, que parece ‘funcionar para mí’:

  • Una gran solución, que contiene el proyecto de aplicación y todos los proyectos de ensamblaje de dependencia
  • He conservado todas las referencias de proyectos, con algunas modificaciones adicionales de dependencias manuales (haga clic con el botón derecho en el proyecto) para algunos ensambles dinámicamente instanciados.
  • Tengo tres carpetas de soluciones (_Working, Synchronized y Xternal), dado que mi control de fuente no está integrado con VS ( sollozos ), esto me permite arrastrar y soltar proyectos entre _Working y Synchronized para que no pierda la pista de cambios La carpeta XTernal es para ensamblajes que “pertenecen” a colegas.
  • Me he creado una configuración ‘WorkingSetOnly’ (última opción en el menú desplegable Depurar / Versión), que me permite limitar los proyectos que se reconstruyen en F5 / F6.
  • En lo que respecta al disco, tengo todas las carpetas de proyectos en una de las pocas carpetas (por lo tanto, solo un nivel de categorización por encima de los proyectos)
  • Todos los proyectos se crean (dll, pdb y xml ) en la misma carpeta de salida y tienen la misma carpeta que una ruta de referencia. (Y todas las referencias están configuradas como No copiar): esto me deja la opción de descartar un proyecto de mi solución y pasar fácilmente a la referencia de archivo (tengo una macro para eso).
  • Al mismo nivel que mi carpeta ‘Proyectos’, tengo una carpeta ‘Soluciones’, donde mantengo soluciones individuales para algunos ensambles, junto con el código de prueba (por ejemplo) y la documentación / diseño, etc., específicos para el ensamblaje.

Esta configuración parece estar funcionando bien para mí en este momento, pero la gran prueba será intentar venderla a mis colegas, y ver si volará como una configuración de equipo.

Inconvenientes actualmente no resueltos:

  • Todavía tengo un problema con las soluciones de ensamblaje individuales, ya que no siempre quiero incluir todos los proyectos dependientes. Esto crea un conflicto con la solución ‘maestra’. He solucionado esto con (una vez más) una macro que convierte referencias de proyectos rotos a referencias de archivos y restaura referencias de archivos a referencias de proyecto si el proyecto se vuelve a agregar.
  • Lamentablemente, no hay forma (que he encontrado hasta ahora) de vincular la Configuración de comstackción a las Carpetas de solución. Sería útil poder decir “comstackr todo en esta carpeta”, tal como está, debo actualizar esto a mano ( doloroso y fácil de olvidar). (Puede hacer clic con el botón derecho en una Carpeta de Solución para comstackr, pero eso no maneja el escenario F5)
  • Existe un error (menor) en la implementación de la carpeta Solución, lo que significa que cuando vuelve a abrir una solución, los proyectos se muestran en el orden en que se agregaron, y no en orden alfabético. (He abierto un error con MS , aparentemente ahora corregido, pero supongo que para VS2010)
  • Tuve que desinstalar el complemento CodeRushXPress porque se estaba ahogando en todo ese código, pero esto fue antes de haber modificado la configuración de comstackción, así que voy a intentarlo de nuevo.

Resumen: cosas que no sabía antes de hacer esta pregunta que han resultado útiles:

  1. Uso de carpetas de soluciones para organizar soluciones sin jugar con el disco
  2. Creación de configuraciones de comstackción para excluir algunos proyectos
  3. Poder definir dependencias manualmente entre proyectos, incluso si están usando referencias de archivos

Esta es mi pregunta más popular, así que espero que esta respuesta ayude a los lectores. Todavía estoy muy interesado en más comentarios de otros usuarios.