Como estruturar um aplicativo Go, arquitetado de acordo com a arquitetura limpa

9

Estou tentando criar um projeto usando a arquitetura limpa, conforme descrito aqui . Encontrei um ótimo artigo sobre como fazer isso no Go .

O exemplo é muito simples, e o autor coloca seu código em pacotes nomeados com base na camada em que estão. Gosto da ideia do tio Bob de que a arquitetura de um aplicativo deve comunicar claramente sua intenção . Então, eu gostaria que meu aplicativo tivesse pacotes de nível superior baseados em áreas de domínio. Portanto, minha estrutura de arquivos ficaria assim:

/Customers
    /domain.go
    /interactor.go
    /interface.go
    /repository.go
/... the same for other domain areas

O problema é que várias camadas compartilham o mesmo pacote. Portanto, não está claro quando a regra de dependência está sendo violada, porque você não tem importações mostrando o que depende de quê.

Estou vindo de um plano de fundo do Python, onde isso não seria um problema, porque você pode importar arquivos individuais, assim como customers.interactorimportar customers.domain.

Poderíamos alcançar algo semelhante no gO aninhando pacotes, para que o pacote customers contenha um pacote de domínio e um pacote de interação, e assim por diante. Isso parece desajeitado, e pacotes com nomes idênticos podem ser irritantes de se lidar.

Outra opção seria criar vários pacotes por área de domínio. Um chamado customer_domain, outro chamado customer_interactor, etc. Mas isso também parece sujo. Ele não se encaixa bem nas diretrizes de nomenclatura de pacotes do Go e parece que todos esses pacotes separados devem, de alguma forma, ser agrupados, já que seus nomes têm um prefixo comum.

Então, qual seria um bom layout de arquivo para isso?

Grande cego
fonte
Como alguém que sofreu uma arquitetura que organizava pacotes exclusivamente por nível de arquitetura, permita-me expressar meu suporte a pacotes baseados em recursos . Sou a favor da comunicação de intenções, mas não me faça rastejar por todos os cantos e recantos obscuros apenas para adicionar um novo recurso.
candied_orange
Por intenção, quero dizer que os pacotes devem comunicar o que é o aplicativo. Portanto, se você estiver criando um aplicativo de biblioteca, terá um pacote de livros, um pacote de credores etc.
bigblind 4/17/17

Respostas:

5

Existem algumas soluções para isso:

  1. Separação de Pacotes
  2. Análise de Revisão
  3. Análise estática
  4. Análise de tempo de execução

Cada um com seus prós / contras.

Separação de Pacotes

Essa é a maneira mais fácil que não requer construção de nada extra. Ele vem em dois sabores:

// /app/user/model/model.go
package usermodel
type User struct {}

// /app/user/controller/controller.go
package usercontroller
import "app/user/model"
type Controller struct {}

Ou:

// /app/model/user.go
package model
type User struct {}

// /app/controller/user.go
package controller
import "app/user/model"

type User struct {}

Isso, no entanto, quebra a totalidade do conceito de User. Para entender ou modificar, Uservocê precisa tocar em vários pacotes.

Porém, ele tem uma boa propriedade que é mais óbvia ao modelimportar controllere, até certo ponto, é imposta pela semântica da linguagem.

Análise de Revisão

Se o aplicativo não for grande (menos de 30 KB) e você tiver bons programadores, normalmente não é necessário criar nada. Organizar estruturas baseadas em valor será suficiente, por exemplo:

// /app/user/user.go
package user
type User struct {}
type Controller struct {}

Freqüentemente, as "violações de restrição" são pouco significativas ou fáceis de corrigir. Isso prejudica a clareza e a compreensão - desde que você não deixe que fique fora de controle, não precisa se preocupar com isso.

Análise estática / tempo de execução

Você também pode usar a análise estática ou de tempo de execução para encontrar essas falhas, através de anotações:

Estático:

// /app/user/user.go
package user

// architecture: model
type User struct {}

// architecture: controller
type Controller struct {}

Dinâmico:

// /app/user/user.go
package user

import "app/constraint"

var _ = constraint.Model(&User{})
type User struct {}

var _ = constraint.Controller(&Controller{})
type Controller struct {}

// /app/main.go
package main

import "app/constraint"

func init() { constraint.Check() }

Estático / dinâmico também pode ser feito via campos:

// /app/user/user.go
package user

import "app/constraint"

type User struct {   
    _ constraint.Model
}

type Controller struct {
    _ constraint.Controller
}

É claro que a busca por essas coisas se torna mais complicada.

Outras versões

Tais abordagens podem ser usadas em outros lugares, não apenas nas restrições de tipo, mas também na nomeação de funções, API-s etc.

https://play.golang.org/p/4bCOV3tYz7

Egon
fonte