>

¿Cómo estructurar tus proyectos en Go?

Friends of Go     Colaboraciones    12/04/2019

Este artículo ha sido escrito por Joan López de la Franca y publicado originalmente en el blog de Friends of Go


¿Cuántas veces has empezado un nuevo proyecto en Go y te han surgido dudas sobre cómo organizar tú código?
Por suerte, o por desgracia, la mayoría de los que estamos metidos en esto venimos de entornos como Java, PHP o C#, dónde el uso de namespaces está extendido como una práctica habitual. Sin embargo, cuándo uno empieza a programar en Go y descubre cómo funciona su sistema de paquetes, tiende a quedarse bloqueado. Al fin y al cabo, no debemos olvidar que Go es un lenguaje que lleva la simplicidad al extremo.

gopher working hard - Ashley McNamara

Echando un vistazo a la comunidad

Por suerte, a día de hoy ya podemos considerar que la comunidad de Go es suficientemente grande como ser tomada como referencia. Kubernetes, Moby, CockroachDB, Gorilla Mux, Packer, entre otros, son proyectos con la madurez necesaria para ser tomados como ejemplo a la hora de estructurar nuestras aplicaciones. Más allá de simples Hola Mundo o proyectos personales, estos tienen un gran bagaje en entornos de producción con altas cargas. Asi que, echemos un vistazo a ver qué conclusiones podemos sacar de estos proyectos.

En primer lugar, si nos centramos en librerías, pensadas para ser usadas como paquetes (las dependencias de toda la vida), como, por ejemplo, las típicas de Gorilla (websocket, mux, etc), podemos ver que están estructuradas como un solo paquete. Lo que significa que tendremos nuestra librería desglosada en diferentes ficheros (que se corresponderán con cada uno de los componentes de nuestra librería), pero todos estarán en el directorio raíz y formarán parte del mismo paquete (package).

En segundo lugar, podemos ver como en proyectos más complejos, tipo aplicaciones, como lo es Packer de HashiCorp, se sigue una estructura multinivel, similar a la que se sigue en otros lenguajes. De forma que, tenemos una primera estructura de directorios que representa cada uno de los componentes de nuestra aplicación, y luego, en cada directorio, tenemos, o bien ya código Go, o bien un segundo nivel de carpetas, que representa cada uno de los subcomponentes que componen el componente padre.

Los que ya lleven un tiempo peleándose con los paquetes de Go, seguramente pensarán que esta opción no es muy aconsejable. Sin embargo, si recurrimos al core del lenguaje, nos encontramos con algo parecido, como lo son los paquetes agrupados. Véase "net/http" o "crypto/md5". Sea como sea, no debemos olvidar que, cuándo procedamos a consumir dichos paquetes, el único nombre que figurará en nuestro código es el último de la cadena. Por ejemplo http.Client{}.

En tercer y último lugar, podemos observar otro patrón habitual. El comúnmente denominado cómo cmd + pkg. Dónde, todo el código correspondiente a los diferentes componentes de nuestra aplicación, iría dentro de la carpeta pkg, mientras que, el código correspondiente a los puntos de entrada de nuestra aplicación (CLI, API, etc) iría en el directorio cmd. Incluso se puede llegar a ver una combinación de las dos últimas.

Reflexión: ¿Qué tipo de proyecto estás construyendo?

Después de echar un vistazo por algunos de los proyectos escritos en Go más populares, nos podemos dar cuenta que no hemos sacado mucho en claro. Sin embargo, parece que sí hemos llegado a una conclusión. Antes de plantearnos cómo estructurar nuestras aplicaciones, debemos pensar bien la respuesta a la siguiente pregunta:

- ¿Qué tipo de proyecto vamos a construir?

Ante esta cuestión, tenemos varias posibles respuestas. Alguna de ellas ya la hemos anticipado anteriormente:

  • ¿Quizás una librería que funcionará por si sola y será usada como dependencia en otras aplicaciones?

    Véase una librería para la gestión de protocol buffers.

  • ¿Quizás una aplicación que funcionará estrictamente como tal, sin tener funcionalidades exportadas como librerías?

    Véase una aplicación que expondrá una API REST de nuestro negocio.

  • ¿Quizás una aplicación, que además también tendrá funcionalidades expuestas como paquetes?

    Véase una aplicación CLI de transformación de imágenes que también se podrá usar como librería en otros desarrollos.

Como vemos, las posibles respuestas a dicha pregunta, son varias, sin embargo, parece que la mayor duda surge cuándo estamos dispuestos a construir una aplicación grande y compleja, no cuándo vamos a desarrollar una "simple" librería. Pues, en ese último caso, como ya adelantemos anteriormente, lo que haremos será tener un único paquete con varios ficheros.

¿Cómo estructurar tus aplicaciones en Go?

Como hemos visto, parece que, a pesar de que hay decisiones ya estandarizadas, aún no hay ningún enfoque que se haya convertido en el referente absoluto. Es por eso que, desde Friends of Go, queremos hacer nuestra propuesta sobre como estructurar vuestras aplicaciones en Go. Por su puesto, ésta no es más que una confluencia de varias de las estrategias que ya se siguen hoy día en la comunidad. Ya tenemos suficiente con mojarnos y dar nuestra opinión al respecto, solo nos faltaba decir que, además, la hemos inventado nosotros, lo cuál no sería verdad.

Entonces, ¿qué proponemos? Veamos!

your-application-name
├── cmd
|   ├── your-application-name-cli
|   |    └── main.go
|   ├── your-application-name-api
|   |    └── main.go
├── internal/
|   ├── package01
|   |    ├── file01.go
|   |    └── file02.go
|   ├── package02
|   |    ├── file01.go
|   |    └── file02.go
├── pkg/
|   ├── library01
|   |    ├── file01.go
|   |    └── file02.go
|   ├── library02
|   |    ├── file01.go
|   |    └── file02.go
├── vendor/
├── LICENSE
└── README.md

Dónde cada carpeta (o estructura de carpetas), tendría su finalidad:

  • /cmd, será la carpeta que va a contener todos los puntos de entrada de nuestra aplicación. Cada uno de ellos, será un paquete de la carpeta cmd y cada uno tendrá su main.go además del resto de código necesario (configuraciones, entornos, inyección de dependencias, etc). Es importante recordar que, los paquetes dentro de cmd se corresponderán con el nombre de los puntos de entrada, es decir, con el nombre de los binarios de las aplicaciones correspondientes.

  • /internal, será la carpeta que va a contener todos los paquetes que representan los componentes de nuestra aplicación, que no serán expuestos y que no van a tener por qué funcionar fuera de ella.

  • /pkg, será la carpeta que va a contener todos aquellos componentes de nuestra aplicación que sí queremos exponer, y que van a tener que poder funcionar fuera de ella (reutilización).

  • /vendor, (opcional) será la carpeta que va a contener todas las dependencias de terceros en caso de que aún no hayamos migrado a Go Modules o que aún sigamos una estrategia de vendoring.

Como podemos ver, ésta estrategia nos permite cubrir los distintos casos planteados anteriormente, pero no solo eso, sinó que además, estamos indicando, con nuestra estructura de carpetas, qué paquetes van a ser expuestos y cuáles no, de forma fácil e intuitiva. Sobretodo, si tenemos en cuenta que los paquetes dentro del directorio internal directamente no van a poder ser importados desde fuera de nuestro proyecto (por defecto).

Kit, el Shared Kernel más gopher

Cuándo hablamos de Domain-Driven Design (DDD), es habitual hablar del concepto Shared Kernel, el cuál hace referencia a todo el conjunto de tipos de datos y funciones asociadas que son compartidas por los diferentes contextos de nuestro dominio.

En la comunidad Go, los compañeros de ArdanLabs proponen lo que ellos han llamado Kit. Concepto con el que queremos cerrar éste artículo.

Según su definición, Kit debería ser el proyecto que representa la biblioteca estándar de tu organización, y ésta debería ser única. Es por eso, que los paquetes que formen parte de la misma, deben diseñarse teniendo especial cuidado con la portabilidad de los mismos. Los paquetes dentro de Kit deberían poder ser utilizados en múltiples aplicaciones y proporcionar un dominio de funcionalidad específico. Recordad, menos utils, common y shared y más time, strings y crypto.