Estrucutra de una aplicación

En este post vamos a ver cómo debemos estructurar una aplicación, ya bien sea web como es este caso o una aplicación de consola, o quizá tengamos una aplicación que contenga tanto una aplicación web, como varias de consola o varias lambda/azure functions, la idea es la misma. 

La organización de carpetas y subproyectos es crucial cuando nuestro proyecto principal crece, ya que una buena organización nos dará una forma más fácil de trabajar en el. 

 

1 - Estructura de carpetas de un repositorio en C#

Esta parte es más opinión personal / comunidad open source que algo que tiene que ser asi si o sí. Pero en las comunidades open source se lleva por convención una estructura como la siguiente;

Partimos de nuestro repositorio en GitHub  (aunque nos sirve cualquier controlador de versiones).

En la carpeta padre (root) debemos incluir el .gitignore, licencia y readme, y dentro no ponemos el código directamente, sino que hacemos una carpeta denominada src (código fuente).

En caso de que tengamos algún otro contenido en nuestro proyecto, como por ejemplo la documentación irá en esta carpeta root también.

estructura repo c#

Una vez estamos dentro de la carpeta src debemos crear una carpeta para cada uno de los “proyectos” que contenga ese proyecto más grande.

En nuestro caso particular por ahora tenemos una API para el back end, y el front end está previsto hacerlo en Blazor. Para ello creamos dos carpetas, una para cada uno de los proyectos.

Dentro de estas carpetas NO escribimos nuestro código, sino que tenemos otras dos carpetas, una que especifica src, como la anterior, y una segunda carpeta llamada test.

 

En conjunción con las carpetas de nuestros proyectos, si tenemos código común, debemos ponerlo en un apartado común o compartido, comúnmente llamado Shared. Dentro de la carpeta Shared no es común tener test pero podemos incluirlos. para nuestro ejemplo únicamente introduciré la librería de railway oriented programming.

 

Y sin olvidarnos de los test de integración, los cuales deben integrar toda la aplicación por lo que también tienen su carpeta independiente.

estructura proyectos c#

 

Esta estructura hay que seguirla para intentar que nuestro código sea lo mas entendible posible, ya que si nos ponemos a crear los proyectos donde queramos, no solo el código, sino que la estructura pasará a ser un caos. 

 

Seguir una buena estructura de carpetas también nos indicará cuando podemos o no referenciar proyectos desde otros proyectos, sabiendo que, si nos salimos de la carpeta padre del proyecto (BackEnd/Frontend en nuestro caso), únicamente podemos referenciar shared, si referenciamos a cualquier otro proyecto, sabemos que lo estamos haciendo mal.

referencias entre proyectos c#

 

2 - Estructura por proyecto

Debemos crear nuestro código con las capas del proyecto bien separadas e indicadas, comúnmente estas capas irán en forma de “librería” y deberán contener el nombre completo. Esto que quiere decir?

En nuestro ejemplo, estamos trabajando en un proyecto llamado WebPersonal y dentro de la web personal, la API está dentro de lo que hemos denominado como “BackEnd” por lo tanto el nombre completo de la api sera WebPersonal.BackEnd.API.

Y esto lo debemos hacer con TODOS nuestros proyectos, da igual en que “subproyecto” o apartado estemos ya que de esa forma, introducimos una mayor claridad a la aplicación

 

2.1 - El Modelo

Principalmente debemos crear nuestro código que sea lo menos dependiente posible.

Por lo que debemos crear nuestro modelo, el modelo es la parte central de nuestro proyecto, donde definimos los tipos y debemos intentar que NO contenga dependencias externas, sobre todo a librerías de terceros. 

 

Cada una de nuestras aplicaciones dentro del repositorio debe tener un Modelo, osea debemos crear un modelo por aplicación. 

 

2.2 - Acceso a datos

Para acceder a nuestra base de datos también vamos a tener nuestra librería con el acceso a esta base de datos. 

 

2.3 - Servicio

Definimos los servicios como el proyecto que contiene la lógica de negocio de nuestro proceso concreto, osea la lógica de “la función” que vamos a ejecutar, si recordamos del post de Railway oriented programming junto con los principios SOLID, cada método o función debe realizar una única accion.

Es aquí en esta librería donde colocaremos esta parte de la lógica, por ejemplo, si tenemos un endpoint en la API que nos lee el perfil personal por ID, tendremos un servicio que realiza únicamente la acción de coger un perfil personal pasando un id por parámetro. 

 

2.4 - Dependencias del servicio

Posteriormente debemos crear lo que llamamos ServiceDependencies este proyecto de librería contendrá todas las dependencias de nuestro servicio.

Denominamos dependencia a todo dato o proceso que nuestro servicio debe acceder o consultar. 

Por ejemplo, el sistema de archivos, si tenemos que leer un fichero, la base de datos o incluso otra API ya sea de nuestro proyecto o de otro.

 

Comunmente en programación orientada a objetos utilizamos interfaces estas interfaces representan lo que va a ser la clase en sí. Debemos hacer las interfaces en el proyecto de los servicios. 

Esto es debido a que así, debemos añadir una referencia desde ServiceDependencies a nuestro servicio. Lo que nos prevendrá de añadir referencias circulares. 

Ya que el proyecto serviceDependencies tendrá referencias tanto al proyecto de acceso a la base de datos (por ejemplo) como al proyecto de los servicios, esta referencia evitará que podamos referenciar desde los servicios a la base de datos directamente, por ejemplo. 

 

Por supuesto para aplicar este patrón directamente debemos aplicar inversión de dependencias.

 

2.5 - Punto de entrada

En nuestro caso particular es una API, pero este punto se aplica también a aplicaciones de consola o a lambdas. Y es que debemos definir el punto de entrada de nuestra aplicación.

Debe seguir la misma estructura de nombres y tendrá acceso a la class library de los servicios, y los servicios serán llamados desde el punto de entrada. De esta forma evitaremos referencias circulares de una forma muy sencilla.

 

2.6 - Imágen descriptiva

estructura proyecto c#

 

2.5 - Fichero MSBuild

Es también una muy buena práctica tener un fichero que engloba todas las características comunes de nuestros proyectos, como por ejemplo la versión del lenguaje que utilizamos.

Por ejemplo podemos limitar la versión del lenguaje a C#7.1 y así no utilizar funcionalidades de C#8.0 aun utilizando netcore 3.1.

De esta forma nos aseguramos que TODOS los proyectos que tenemos dentro de nuestro repositorio, comparten la misma versión del lenguaje y los podemos actualizar todos a la vez. 

 

Es también un lugar para indicar el autor, empresa, nombre del producto, copyright o la versión que van a ser comunes en todos nuestros proyectos. así como las configuraciones de Release y Debug.

Es común el uso de el fichero MSBuild para indicar los ficheros de configuración de las diferentes aplicaciones, aunque no es obligatorio al 100%

Aquí podemos ver un ejemplo del fichero

<Project>
  <PropertyGroup>
    <Authors>Ivan Abad</Authors>
    <Company>NetMentor</Company>
    <Product>WebPersonal</Product>
    <Copyright>Copyright NetMentor 2020</Copyright>
    <Version>1.0.0</Version>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <Description>Configuration=Debug</Description>
    <Configuration>Debug</Configuration>
    <Description>Configuration=Debug</Description>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <Description>Configuration=Release</Description>
    <Configuration>Release</Configuration>
    <Description>Configuration=Release</Description>
  </PropertyGroup>
  <PropertyGroup>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
	<LangVersion>8.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <None Update="API.json" CopyToOutputDirectory="PreserveNewest" />
    <None Update="API.Production.json" CopyToOutputDirectory="PreserveNewest" />
    <None Update="API.Staging.json" CopyToOutputDirectory="PreserveNewest" />
    <None Update="API.Internal.json" CopyToOutputDirectory="PreserveNewest" />
    <None Update="API.Development.json" CopyToOutputDirectory="PreserveNewest" />
    <None Update="API.user.json" CopyToOutputDirectory="PreserveNewest" />
   </ItemGroup>
</Project>

Por supuesto debemos referenciar este fichero desde cada uno de los .csproj que queramos aplicar esta configuración. Un nombre correcto para este fichero seríá GlobalSettings.msbuild

 

3 - Implementación en el código

Obviamente durante el post no puedo poner como crear proyectos en el código, para eso, podéis acceder al vídeo -disponible el 14 de julio -, pero sí puedo indicar cómo y dónde haremos hacer el cambio para aplicar los nombres correctamente a un proyecto en C#.

 

Para cambiar el nombre de un proyecto, debemos ir a su .csproj, y veremos que se nos abre en forma de xml, uno de los tags es llamado <PropertyGroup> en el podemos tener varios elementos, el que estamos buscando es <RootNamespace> y ahí debemos indicar el nombre.

 

Opcionalmente yo recomiendo activar <TreatWarningsAsErrors> así deberemos arreglar los warnings antes de compilar. 

<PropertyGroup>
   <TargetFramework>netcoreapp3.1</TargetFramework>
   <RootNamespace>WebPersonal.BackEnd.API</RootNamespace>
   <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

 

Conclusión

En este post hemos visto la estructura de carpetas que debe seguir nuestro repositorio.

Así como la estructura que debe seguir cada uno de los proyectos, explicando sus librerías principales. 

Encarecidamente recomiendo utilizar una buena estructura en vuestros proyectos ya que ayudará muchísimo a la hora de programar y entender el contenido del mismo.

Por supuesto si seguimos el patrón que he indicado en el punto 2 evitaremos errores de referencias circulares, que suelen ser un gran problema en códigos antiguos.