├── .gitignore ├── README.md ├── cmd └── app │ └── main.go ├── go.mod ├── go.sum ├── img.png ├── internal ├── adapters │ └── db │ │ └── mongodb │ │ ├── author.go │ │ ├── book.go │ │ └── genre.go ├── config │ └── config.go ├── controller │ ├── amqp │ │ └── dto │ │ │ └── book.go │ └── http │ │ ├── dto │ │ ├── author.go │ │ ├── book.go │ │ └── genre.go │ │ └── v1 │ │ ├── author.go │ │ ├── book.go │ │ └── genre.go └── domain │ ├── entity │ ├── author.go │ ├── book.go │ └── genre.go │ ├── service │ ├── author.go │ ├── book.go │ └── genre.go │ └── usecase │ └── book │ ├── book.go │ └── dto.go └── pkg ├── client └── mongodb │ └── client.go └── logging └── logging.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # golang-clean-architecture 2 | 3 | Этот репозиторий, артефакт 3 роликов про Чистую Архитектуру на примере языка Go. 4 | 5 | - Часть 4 - https://youtu.be/B_GUqUO42cA (текущий код) 6 | - Часть 3 - https://youtu.be/PqQyCFygiZg 7 | - Часть 2 - https://youtu.be/s_Bou_mChKs 8 | - Часть 1 - https://youtu.be/eVhIlhLl4e4 9 | 10 | -------------------------------------------------------------------------------- /cmd/app/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // TODO composition 5 | } 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module ca-library-app 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/ilyakaznacheev/cleanenv v1.2.6 7 | github.com/julienschmidt/httprouter v1.3.0 8 | github.com/sirupsen/logrus v1.8.1 9 | go.mongodb.org/mongo-driver v1.8.1 10 | ) 11 | 12 | require ( 13 | github.com/BurntSushi/toml v0.3.1 // indirect 14 | github.com/go-stack/stack v1.8.0 // indirect 15 | github.com/golang/snappy v0.0.1 // indirect 16 | github.com/joho/godotenv v1.3.0 // indirect 17 | github.com/klauspost/compress v1.13.6 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 20 | github.com/xdg-go/scram v1.0.2 // indirect 21 | github.com/xdg-go/stringprep v1.0.2 // indirect 22 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 23 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect 24 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect 25 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect 26 | golang.org/x/text v0.3.5 // indirect 27 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 28 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 7 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 8 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 9 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 10 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 11 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 12 | github.com/ilyakaznacheev/cleanenv v1.2.6 h1:oJRaVZfAI0xdA5LJNguuKH2ldVJg44SP8GqkEn/cw7w= 13 | github.com/ilyakaznacheev/cleanenv v1.2.6/go.mod h1:C3bB+MJ+LjECYlw2k7CSagKGfL1Ym2ywfjj40RjXJ24= 14 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 15 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 16 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 17 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 18 | github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= 19 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 20 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 21 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 22 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 23 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 24 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 25 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 26 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 27 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 28 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 31 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 34 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 35 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 36 | github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= 37 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 38 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 39 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 40 | github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= 41 | github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= 42 | github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= 43 | github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= 44 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 45 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 46 | go.mongodb.org/mongo-driver v1.8.1 h1:OZE4Wni/SJlrcmSIBRYNzunX5TKxjrTS4jKSnA99oKU= 47 | go.mongodb.org/mongo-driver v1.8.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= 48 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 49 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU= 50 | golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 51 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 52 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 53 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 55 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 56 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 57 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 58 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 62 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 63 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 64 | golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 65 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 66 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 67 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 68 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 69 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 72 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= 74 | olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= 75 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theartofdevel/golang-clean-architecture/1423e9a33f78239e1a39d3ba70fd2e07f0fdc36d/img.png -------------------------------------------------------------------------------- /internal/adapters/db/mongodb/author.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/domain/entity" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | ) 9 | 10 | type authorStorage struct { 11 | db *mongo.Database 12 | } 13 | 14 | func NewAuthStorage(db *mongo.Database) *authorStorage { 15 | return &authorStorage{db: db} 16 | } 17 | 18 | func (bs *authorStorage) GetOne(id string) *entity.Author { 19 | return nil 20 | } 21 | func (bs *authorStorage) GetAll(ctx context.Context) []*entity.Author { 22 | return nil 23 | } 24 | func (bs *authorStorage) Create(book *entity.Author) *entity.Author { 25 | return nil 26 | } 27 | func (bs *authorStorage) Delete(book *entity.Author) error { 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/adapters/db/mongodb/book.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "ca-library-app/internal/domain/entity" 5 | "go.mongodb.org/mongo-driver/mongo" 6 | ) 7 | 8 | type bookStorage struct { 9 | db *mongo.Database 10 | } 11 | 12 | func NewBookStorage(db *mongo.Database) *bookStorage { 13 | return &bookStorage{db: db} 14 | } 15 | 16 | func (bs *bookStorage) GetOne(id string) *entity.Book { 17 | return nil 18 | } 19 | func (bs *bookStorage) GetAll(limit, offset int) []*entity.Book { 20 | return nil 21 | } 22 | func (bs *bookStorage) Create(book *entity.Book) *entity.Book { 23 | return nil 24 | } 25 | func (bs *bookStorage) Delete(book *entity.Book) error { 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /internal/adapters/db/mongodb/genre.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/domain/entity" 7 | "go.mongodb.org/mongo-driver/mongo" 8 | ) 9 | 10 | type genreStorage struct { 11 | db *mongo.Database 12 | } 13 | 14 | func NewGenreStorage(db *mongo.Database) *genreStorage { 15 | return &genreStorage{db: db} 16 | } 17 | 18 | func (bs *genreStorage) GetOne(id string) *entity.Genre { 19 | return nil 20 | } 21 | func (bs *genreStorage) GetAll(ctx context.Context) []*entity.Genre { 22 | return nil 23 | } 24 | func (bs *genreStorage) Create(book *entity.Genre) *entity.Genre { 25 | return nil 26 | } 27 | func (bs *genreStorage) Delete(book *entity.Genre) error { 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "sync" 5 | 6 | "ca-library-app/pkg/logging" 7 | "github.com/ilyakaznacheev/cleanenv" 8 | ) 9 | 10 | type Config struct { 11 | IsDebug *bool `yaml:"is_debug"` 12 | Listen struct { 13 | Type string `yaml:"type" env-default:"port"` 14 | BindIP string `yaml:"bind_ip" env-default:"localhost"` 15 | Port string `yaml:"port" env-default:"8080"` 16 | } 17 | MongoDB struct { 18 | Host string `yaml:"host" env-required:"true"` 19 | Port string `yaml:"port" env-required:"true"` 20 | Username string `yaml:"username"` 21 | Password string `yaml:"password"` 22 | AuthDB string `yaml:"auth_db" env-required:"true"` 23 | Database string `yaml:"database" env-required:"true"` 24 | Collection string `yaml:"collection" env-required:"true"` 25 | } `yaml:"mongodb" env-required:"true"` 26 | } 27 | 28 | var instance *Config 29 | var once sync.Once 30 | 31 | func GetConfig() *Config { 32 | once.Do(func() { 33 | logger := logging.GetLogger() 34 | logger.Info("read application config") 35 | instance = &Config{} 36 | if err := cleanenv.ReadConfig("config.yml", instance); err != nil { 37 | help, _ := cleanenv.GetDescription(instance, nil) 38 | logger.Info(help) 39 | logger.Fatal(err) 40 | } 41 | }) 42 | return instance 43 | } 44 | -------------------------------------------------------------------------------- /internal/controller/amqp/dto/book.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type BookEvent struct { 4 | EventType string 5 | Data struct { 6 | Name string 7 | Year int 8 | AuthorUUID string 9 | GenreUUID string 10 | } 11 | } -------------------------------------------------------------------------------- /internal/controller/http/dto/author.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type CreateAuthorDTO struct { 4 | Name string `json:"name"` 5 | Age int `json:"age"` 6 | } 7 | 8 | type UpdateAuthorDTO struct { 9 | ID string `json:"id"` 10 | Name string `json:"name"` 11 | Age int `json:"age"` 12 | } 13 | -------------------------------------------------------------------------------- /internal/controller/http/dto/book.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type CreateBookDTO struct { 4 | Name string `json:"name"` 5 | Year int `json:"year"` 6 | AuthorUUID string `json:"author_uuid"` 7 | GenreUUID string `json:"genre_uuid"` 8 | } 9 | 10 | type UpdateBookDTO struct { 11 | ID string `json:"id"` 12 | Name string `json:"name"` 13 | Year int `json:"year"` 14 | AuthorUUID string `json:"author_uuid"` 15 | Busy bool `json:"busy"` 16 | Owner string `json:"owner"` 17 | } 18 | -------------------------------------------------------------------------------- /internal/controller/http/dto/genre.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type CreateGenreDTO struct { 4 | ID string `json:"id,omitempty"` 5 | Name string `json:"name,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/controller/http/v1/author.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "ca-library-app/internal/___backup/adapters/api/author" 5 | "ca-library-app/internal/backup/adapters/api" 6 | "github.com/julienschmidt/httprouter" 7 | ) 8 | 9 | type handler struct { 10 | authorService author.Service 11 | } 12 | 13 | func NewHandler(service author.Service) api.Handler { 14 | return &handler{authorService: service} 15 | } 16 | 17 | func (h *handler) Register(router *httprouter.Router) { 18 | panic("implement me") 19 | } 20 | -------------------------------------------------------------------------------- /internal/controller/http/v1/book.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "ca-library-app/internal/controller/http/dto" 9 | "ca-library-app/internal/domain/entity" 10 | book_usecase "ca-library-app/internal/domain/usecase/book" 11 | "github.com/julienschmidt/httprouter" 12 | ) 13 | 14 | const ( 15 | bookURL = "/books/:book_id" 16 | booksURL = "/books" 17 | ) 18 | 19 | type BookUsecase interface { 20 | CreateBook(ctx context.Context, dto book_usecase.CreateBookDTO) (string, error) 21 | ListAllBooks(ctx context.Context) []entity.BookView 22 | GetFullBook(ctx context.Context, id string) entity.FullBook 23 | } 24 | 25 | type bookHandler struct { 26 | bookUsecase BookUsecase 27 | } 28 | 29 | func NewBookHandler(bookUsecase BookUsecase) *bookHandler { 30 | return &bookHandler{bookUsecase: bookUsecase} 31 | } 32 | 33 | func (h *bookHandler) Register(router *httprouter.Router) { 34 | router.GET(booksURL, h.GetAllBooks) 35 | } 36 | 37 | func (h *bookHandler) GetAllBooks(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 38 | // books := h.bookService.GetAll(context.Background(), 0, 0) 39 | w.Write([]byte("books")) 40 | w.WriteHeader(http.StatusOK) 41 | } 42 | 43 | func (h *bookHandler) CreateBook(w http.ResponseWriter, r *http.Request, params httprouter.Params) { 44 | 45 | var d dto.CreateBookDTO 46 | defer r.Body.Close() 47 | if err := json.NewDecoder(r.Body).Decode(&d); err != nil { 48 | return // error 49 | } 50 | 51 | // validate 52 | 53 | // MAPPING dto.CreateBookDTO --> book_usecase.CreateBookDTO 54 | usecaseDTO := book_usecase.CreateBookDTO{ 55 | Name: "", 56 | Year: 0, 57 | AuthorUUID: "", 58 | GenreUUID: "", 59 | } 60 | book, err := h.bookUsecase.CreateBook(r.Context(), usecaseDTO) 61 | if err != nil { 62 | // JSON RPC: TRANSPORT: 200, error: {msg, ..., dev_msg} 63 | return 64 | } 65 | w.WriteHeader(http.StatusOK) 66 | w.Write([]byte(book)) 67 | } 68 | -------------------------------------------------------------------------------- /internal/controller/http/v1/genre.go: -------------------------------------------------------------------------------- 1 | package v1 2 | -------------------------------------------------------------------------------- /internal/domain/entity/author.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type Author struct { 4 | ID string `json:"id,omitempty"` 5 | Name string `json:"name,omitempty"` 6 | Age int `json:"age,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /internal/domain/entity/book.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "fmt" 4 | 5 | type BookView struct { 6 | ID string `json:"id,omitempty"` 7 | Name string `json:"name,omitempty"` 8 | Year int `json:"year,omitempty"` 9 | AuthorName string `json:"author,omitempty"` 10 | GenreName string `json:"genre,omitempty"` 11 | Busy bool `json:"busy,omitempty"` 12 | } 13 | 14 | type FullBook struct { 15 | Book 16 | Author Author `json:"author,omitempty"` 17 | Genre Genre `json:"genre,omitempty"` 18 | } 19 | 20 | type Book struct { 21 | ID string `json:"id,omitempty"` 22 | Name string `json:"name,omitempty"` 23 | Year int `json:"year,omitempty"` 24 | AuthorID string `json:"author_id,omitempty"` 25 | GenreID string `json:"genre_id,omitempty"` 26 | Busy bool `json:"busy,omitempty"` 27 | Owner string `json:"owner,omitempty"` 28 | } 29 | 30 | func (b *Book) Take(owner string) error { 31 | if b.Busy { 32 | return fmt.Errorf("book is busy") 33 | } 34 | b.Owner = owner 35 | b.Busy = true 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/domain/entity/genre.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type Genre struct { 4 | ID string `json:"id,omitempty"` 5 | Name string `json:"name,omitempty"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/domain/service/author.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/domain/entity" 7 | ) 8 | 9 | type AuthorStorage interface { 10 | GetOne(id string) entity.Author 11 | GetAll(ctx context.Context) []entity.Author 12 | Create(book entity.Author) entity.Author 13 | Delete(book entity.Author) error 14 | } 15 | 16 | type authorService struct { 17 | storage AuthorStorage 18 | } 19 | 20 | func NewAuthorService(storage AuthorStorage) *authorService { 21 | return &authorService{storage: storage} 22 | } 23 | 24 | func (s authorService) Create(ctx context.Context) entity.Author { 25 | return entity.Author{} 26 | } 27 | 28 | func (s authorService) GetByID(ctx context.Context, id string) entity.Author { 29 | return s.storage.GetOne(id) 30 | } 31 | 32 | func (s authorService) GetAll(ctx context.Context) []entity.Author { 33 | return s.storage.GetAll(ctx) 34 | } 35 | -------------------------------------------------------------------------------- /internal/domain/service/book.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/domain/entity" 7 | ) 8 | 9 | type BookStorage interface { 10 | GetOne(id string) entity.Book 11 | GetAll(limit, offset int) []entity.Book 12 | Create(book entity.Book) entity.Book 13 | Delete(book entity.Book) error 14 | } 15 | 16 | type bookService struct { 17 | storage BookStorage 18 | } 19 | 20 | func NewBookService(storage BookStorage) *bookService { 21 | return &bookService{storage: storage} 22 | } 23 | 24 | func (s bookService) Create(ctx context.Context) entity.Book { 25 | return entity.Book{} 26 | } 27 | 28 | func (s bookService) GetByID(ctx context.Context, id string) entity.Book { 29 | return s.storage.GetOne(id) 30 | } 31 | 32 | func (s bookService) GetAll(ctx context.Context, limit, offset int) []entity.Book { 33 | return s.storage.GetAll(limit, offset) 34 | } 35 | 36 | func (s bookService) GetAllForList(ctx context.Context) []entity.BookView { 37 | // TODO implement 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/domain/service/genre.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/domain/entity" 7 | ) 8 | 9 | type GenreStorage interface { 10 | GetOne(id string) entity.Genre 11 | GetAll(limit, offset int) []entity.Genre 12 | Create(genre entity.Genre) entity.Genre 13 | Delete(genre entity.Genre) error 14 | } 15 | 16 | type genreService struct { 17 | storage GenreStorage 18 | } 19 | 20 | func NewGenreService(storage GenreStorage) *genreService { 21 | return &genreService{storage: storage} 22 | } 23 | 24 | func (s *genreService) Create(ctx context.Context) entity.Genre { 25 | return entity.Genre{} 26 | } 27 | 28 | func (s *genreService) GetByID(ctx context.Context, id string) entity.Genre { 29 | return s.storage.GetOne(id) 30 | } 31 | 32 | func (s *genreService) GetAll(ctx context.Context, limit, offset int) []entity.Genre { 33 | return s.storage.GetAll(limit, offset) 34 | } 35 | -------------------------------------------------------------------------------- /internal/domain/usecase/book/book.go: -------------------------------------------------------------------------------- 1 | package book_usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "ca-library-app/internal/controller/http/dto" 7 | "ca-library-app/internal/domain/entity" 8 | ) 9 | 10 | type Service interface { 11 | GetAllForList(ctx context.Context) []entity.BookView 12 | GetByID(ctx context.Context, id string) entity.Book 13 | } 14 | 15 | type AuthorService interface { 16 | GetByID(ctx context.Context, id string) entity.Author 17 | } 18 | 19 | type GenreService interface { 20 | GetByID(ctx context.Context, id string) entity.Genre 21 | } 22 | 23 | type bookUsecase struct { 24 | bookService Service 25 | authorService AuthorService 26 | genreService GenreService 27 | } 28 | 29 | func (u bookUsecase) CreateBook(ctx context.Context, dto dto.CreateBookDTO) (string, error) { 30 | return "", nil 31 | } 32 | 33 | func (u bookUsecase) ListAllBooks(ctx context.Context) []entity.BookView { 34 | // отобразить список книг с именем Жанра и именем Автора 35 | return u.bookService.GetAllForList(ctx) 36 | } 37 | 38 | func (u bookUsecase) GetFullBook(ctx context.Context, id string) entity.FullBook { 39 | book := u.bookService.GetByID(ctx, id) 40 | author := u.authorService.GetByID(ctx, book.AuthorID) 41 | genre := u.genreService.GetByID(ctx, book.GenreID) 42 | 43 | return entity.FullBook{ 44 | Book: book, 45 | Author: author, 46 | Genre: genre, 47 | } 48 | } 49 | 50 | // pagination 51 | func (u bookUsecase) GetBooksWithAllAuthors(ctx context.Context, id string) []entity.BookView { 52 | // Book{Authors: [all authors]} 53 | // book, author(book_id) -=- 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/domain/usecase/book/dto.go: -------------------------------------------------------------------------------- 1 | package book_usecase 2 | 3 | type CreateBookDTO struct { 4 | Name string 5 | Year int 6 | AuthorUUID string 7 | GenreUUID string 8 | } 9 | -------------------------------------------------------------------------------- /pkg/client/mongodb/client.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go.mongodb.org/mongo-driver/mongo" 7 | options "go.mongodb.org/mongo-driver/mongo/options" 8 | "time" 9 | ) 10 | 11 | func NewClient(ctx context.Context, host, port, username, password, database, authSource string) (*mongo.Database, error) { 12 | var mongoDBURL string 13 | var anonymous bool 14 | if username == "" || password == "" { 15 | anonymous = true 16 | mongoDBURL = fmt.Sprintf("mongodb://%s:%s", host, port) 17 | } else { 18 | mongoDBURL = fmt.Sprintf("mongodb://%s:%s@%s:%s", username, password, host, port) 19 | } 20 | reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 21 | defer cancel() 22 | clientOptions := options.Client().ApplyURI(mongoDBURL) 23 | if !anonymous { 24 | clientOptions.SetAuth(options.Credential{ 25 | AuthSource: authSource, 26 | Username: username, 27 | Password: password, 28 | PasswordSet: true, 29 | }) 30 | } 31 | client, err := mongo.Connect(reqCtx, clientOptions) 32 | if err != nil { 33 | return nil, fmt.Errorf("failed to create client to mongodb due to error %w", err) 34 | } 35 | 36 | err = client.Ping(context.Background(), nil) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to create client to mongodb due to error %w", err) 39 | } 40 | 41 | return client.Database(database), nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/logging/logging.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | "runtime" 9 | "sync" 10 | 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type Logger struct { 15 | *logrus.Entry 16 | } 17 | 18 | func (s *Logger) ExtraFields(fields map[string]interface{}) *Logger { 19 | return &Logger{s.WithFields(fields)} 20 | } 21 | 22 | var instance Logger 23 | var once sync.Once 24 | 25 | func GetLogger(level string) Logger { 26 | once.Do(func() { 27 | logrusLevel, err := logrus.ParseLevel(level) 28 | if err != nil { 29 | log.Fatalln(err) 30 | } 31 | 32 | l := logrus.New() 33 | l.SetReportCaller(true) 34 | l.Formatter = &logrus.TextFormatter{ 35 | CallerPrettyfier: func(f *runtime.Frame) (string, string) { 36 | filename := path.Base(f.File) 37 | return fmt.Sprintf("%s:%d", filename, f.Line), fmt.Sprintf("%s()", f.Function) 38 | }, 39 | DisableColors: false, 40 | FullTimestamp: true, 41 | } 42 | 43 | l.SetOutput(os.Stdout) 44 | l.SetLevel(logrusLevel) 45 | 46 | instance = Logger{logrus.NewEntry(l)} 47 | }) 48 | 49 | return instance 50 | } 51 | --------------------------------------------------------------------------------