Чистая архитектура на Go

Не могу разобраться как внедрять зависимости.

Например у меня есть сущность:

type Car struct {
    ID    int
    Model string
    Price int
 
}

func NewCar(model string, price int) *Car {
    return &Car{
        Model: model,
        Price: price,
    }

}

Для взаимодействия с БД, я сделал следующий интерфейс:

type Storer interface {
    Create(*model.Car) error
    GetByID(int) (*model.Car, error)
}

type Store struct {
    db *sql.DB
}

func NewStore(db *sql.DB) Storer {
    return &Store{
        db: db,
    }
}

func Connectdb() (*sql.DB, error) {
    db, err := sql.Open("mysql", "root:@/go_test")
    if err != nil {
        return nil, err
    }

    if err := db.Ping(); err != nil {
        return nil, err
    }
    return db, nil
}

func (s *Store) Create(c *model.Car) error {
    s.db.QueryRow("INSERT INTO car (model, price) VALUES(?, ?)", c.Model, c.Price).Scan(&c.Model, &c.Price)
    return nil
}

Правильно ли я понимаю, что в конструкторе нужно вернуть именно интерфейс. И потом в структуре где я собираюсь использовать передать этот объект:

type services struct {
    store db.Storer
}

type Service interface {
    Delivery(int) (float64, error)
    CounteryPayment()
}

func NewService(s *db.Storer) Service {
    return &services{
        store: *s,
    }
}

func (s *services) Delivery(id int) (float64, error) {
    u, err := s.store.GetByID(id)
    if err != nil {
        return 0, err
    }
    delivery := u.Price - 250
    return float64(delivery), nil

}

func (s *services) CounteryPayment() {
    return
}

И так далее. Передача объекта интерфейса Service в следующую структуру ?


Ответы (1 шт):

Автор решения: Mark

У вас есть два компонента: сервис и хранилище. Сервис обеспечивает логику, хранилище - взаимодействие с БД. Компоненты находятся в разных пакетах: package service и package storage. В микросервисной архитектуре зачастую достаточно прямого встраивания одних компонентов в другие.

package storage

type Storage struct{}
package service

import "storage"

type Service struct {
    storage *storage.Storage
}

func NewService(storage *storage.Storage) *Service {
    return &Service{storage: storage}
}
package main

import (
    "storage"
    "service"
    )

...

var db *sql.DB // опустим как мы получили соединение с базой 
stor := storage.NewStorage(db)
svc := service.NewService(stor)

Красиво, изящно, просто. Если вы будите добавлять методы для хранилища или менять их сигнатуры, вам не придется каждый раз переписывать интерфейс.

По импорту видно, что основной компонент (сервис) зависит от хранилища и привязан к конкретной реализации, что не очень хорошо.

Давайте уменьшим связность и будем ближе к "чистой архитектуре". Определим интерфейс Storage прямо в пакете service.

package service

type Storage interface {
    Create(*model.Car) error
    GetByID(int) (*model.Car, error)
}

type Service struct {
    storage Storage
}

func NewService(storage Storage) *Service {
    return &Service{storage: storage}
}

Теперь пакеты не зависят друг от друга, а в конструктор сервиса (правильнее фабричную функцию, factory function) можно передавать любой объект, реализующий интерфейс Storage. C точки зрения хранилища вообще ничего не поменялось. Хотя мы можем в пакете storage сделать небольшое дополнение и убедиться, что наше хранилище реализует этот интерфейс:

package storage

import (
   "service"
   )

...

var _ service.Storage = (*Storage)(nil) // статическая проверка

Обратите внимание, что направление зависимости поменялось. Конкретное хранилище зависит от сервиса, но сам сервис не зависит от реализации хранилища, а зависит только от собственного интерфейса. Это и есть последняя буква из акронима SOLID (принципа инверсии зависимостей от Роберта Мартина).

Что касается фабричных функций, то в общем случае они должны возвращать именно тот тип, который в них и создается:

package storage
...
func NewStorage(db *sql.DB) *Storage {
    return &Storage{db: db}
}

Утиная типизация позволит интерпретировать его как нужный интерфейс (в случае хранилища это service.Storage, что собственно мы и видим на примере статической проверки).

→ Ссылка