Напишите фреймворк, похожий на Spring Boot, для Go

Go

Некоторое время назад я написал проект с Kotlin -- spring-boot.

Опыт разработки отличный, он разрушает мой стереотип о "Java"

Основная идея Spring — DI и АОП

Так как же это будет реализовано в Go?

Сначала приведите пример (полную документацию см. в godoc).Rhapsody)

type UserController struct {
	Controller 	`prefix:"api/v1"`
	GET 		`path:":id" method:"GetUserInfo"`
}

func (u *UserController) GetUserInfo(ctx echo.Context) error {
	// do something
}

type Root struct {
	*UserController
}

func main() {
	CreateApplication(new(Root)).Run()
}
(Примечание: веб-модуль этого проекта основан наecho)

Этот пример относительно прост: необходимо зарегистрировать группу маршрутизации prefix = api/v1, а затем зарегистрировать контроллер с путем = :id в этой группе маршрутизации.

Роль контроллера заключается в отметке, что эквивалентно @Controller() в spring, и то же самое верно для GET
Вы также можете отметить в предыдущем слое, как
type UserController struct {
	Controller 	
	GET 		`path:":id" method:"GetUserInfo"`
}

type Root struct {
	*UserController `prefix:"api/v1"`
}
или
type UserController struct {	
	GET 		`path:":id" method:"GetUserInfo"`
}

type Root struct {
	*UserController `type:"controller" prefix:"api/v1"`
}
(Примечание: если оба слоя имеют аннотации, более высокий уровень (Root) переопределит аннотации нижнего уровня (UserController))

Это, кажется, не имеет большого преимущества, потому что внедрение зависимостей не играет большой роли.

Что, если мы напишем другое расширение ORM

Например, я хочу написать расширение GORM под названием GormConfig.

После этого мне просто нужно

type Root struct {
	*UserController `prefix:"api/v1"`
        *GormConfig
        *Entities
}

type Entities struct {
        *User
        *Score
        // Your other models ...
}

могу

type UserController struct {
	Controller 	`prefix:"api/v1"`
	GET 		`path:":id" method:"GetUserInfo"`
        db *gorm.DB
}

func (u *UserController) GetUserInfo(ctx echo.Context) error {
	db.Create(&User{//balabala})
        // do something
}

Как инициализировать БД и как настроить подключение к БД?

Конфигурация естественно прописана в конфигурационном файле

Путь по умолчанию для файла конфигурации — «./resources/application.conf».

Тип по умолчанию — json.

Если вы хотите использовать yaml, просто измените суффикс файла на .yaml или .yml.

Как установить собственный путь?

type Root struct {
        CONF            `path:"./conf/config.json" type:"yaml"`
	*UserController `prefix:"api/v1"`
        *GormConfig
        *Entities
}

Это оно

Примечание. Соглашение лучше, чем конфигурация, конфигурация предшествует принципу соглашения, config.json в приведенном выше коде будет проанализирован как yaml
Поэтому, пожалуйста, уменьшите конфигурацию, насколько это возможно.

После прочтения конфигурационного файла, как нам получить нужное значение?

Например, файл конфигурации говорит:

rhapsody:
  db:
    type: mysql
    database: rhapsody_demo
    username: gopher
    password: gopherLOVErhapsody
  redis:
    host: 127.0.0.1
    port: 6937

Так как же нам написать наше расширение GORM?

нам просто нужно

type SqlParameter struct {
        Parameter
        Type *string      `value:"rhapsody.db.type"`
        Database *string  `value:"rhapsody.db.database"`
        Username *string  `value:"rhapsody.db.username"`
        Password *string  `value:"rhapsody.db.password"`
}

type GormConfig struct {
        Configuration
        App *Application
}

func (g *GormConfig) GetDB(params *SqlParameter) *gorm.DB {
        db, err := gorm.Open(*params.Type, fmt.Sprintf("%s:%s@/%s", *params.Username, *params.Password, *params.Database))
        if err != nil {
                g.App.Logger.Error(//balabala)   
        }
        for _, value := range g.App.Entities {
                db.AutoMigrate(value.interface())
        }
        return db
}
Примечание. Зачем использовать указатели для внедрения строки файла конфигурации?
Поскольку набор spring-boot слишком медленный для однократного запуска, ему необходимо проанализировать зависимости, загрузить и внедрить
Так что рапсодия будет поддерживать горячее обновление конфигурационных файлов, удобнее использовать указатели (что, хотите обновить полностью? Тогда ждите, пока я изучу этоqlangДавайте посмотрим)

Что такое * Приложение здесь?

Фактически это значение, возвращаемое функцией CreateApplication, указатель на глобальный контейнер

В этом контейнере происходит загрузка и сборка всего приложения.

Если вы объявите общедоступный член типа *Application в любом допустимом bean-компоненте (структура с пометкой Configuration/Service/Repository/Component/Controller/Middlware/Router/Parameter), глобальный контейнер будет внедрен автоматически.

Точно так же GetDB выше недействителен, потому что *gorm.DB не является допустимым bean-компонентом.

нам нужен агент

type UserRepository struct {
        Repository
        Db *gorm.DB
}

func (g *GormConfig) GetDB(params *SqlParameter) *UserRepository {
        db, err := gorm.Open(*params.Type, fmt.Sprintf("%s:%s@/%s", *params.Username, *params.Password, *params.Database))
        if err != nil {
                g.App.Logger.Error(//balabala)   
        }
        for _, value := range g.App.Entities {
                db.AutoMigrate(value.interface())
        }
        return &UserRepository{ Db: db }
}

Теперь, когда мы закончили говорить об основном использовании, давайте обсудим более глубокую проблему, как указать внедряемый компонент?

Например, я зарегистрировал две *gorm.DB в Config

func (g *GormConfig) GetDB(params *SqlParameter) *UserRepository {
        db, err := gorm.Open(*params.Type, fmt.Sprintf("%s:%s@/%s", *params.Username, *params.Password, *params.Database))
        if err != nil {
                g.App.Logger.Error(//balabala)   
        }
        return &UserRepository{ Db: db }
}

func (g *GormConfig) GetAutoMigrateDB(params *SqlParameter) *UserRepository {
        db, err := gorm.Open(*params.Type, fmt.Sprintf("%s:%s@/%s", *params.Username, *params.Password, *params.Database))
        if err != nil {
                g.App.Logger.Error(//balabala)   
        }
        for _, value := range g.App.Entities {
                db.AutoMigrate(value.interface())
        }
        return &UserRepository{ Db: db }
}

На самом деле у всех бобов есть свои имена.

Например, имя компонента, созданного фабричной функцией (методом) выше, является именем функции (метода) по умолчанию, а имя напрямую зарегистрированного компонента является полным именем класса.

При наличии нескольких бинов вводимое поле должно указывать имя бина, например

type UserController struct {
	Controller 	       `prefix:"api/v1"`
	GET 		        `path:":id" method:"GetUserInfo"`
        db *UserRepository  `name:"GetAutoMigrateDB"`
}

Если при этом мы хотим указать по умолчанию *UserRepository

Нам нужно использовать полное имя

type UserController struct {
	Controller 	       `prefix:"api/v1"`
	GET 		       `path:":id" method:"GetUserInfo"`
        db *UserRepository     `name:"*rhapsody.UserRepository"`
}

чтобы указать это

Мы также можем переименовать его

Зарегистрируйтесь в конфигурации

type GormConfig struct {
        Configuration
        App *Application
        *UserRepository     `name:"*UserRepo"`
}

тогда ты можешь

type UserController struct {
	Controller 	       `prefix:"api/v1"`
	GET 		       `path:":id" method:"GetUserInfo"`
        db *UserRepository     `name:"*UserRepo"`
}

Подробно: классификация бобов

Как упоминалось выше, бобы делятся на

Configuration / Service/ Repository / Component/ Controller / Middlware / Router / Parameter

Среди них конфигурация, контроллер, маршрутизатор и промежуточное ПО могут быть напрямую зарегистрированы в корневом каталоге.

Контроллер используется для регистрации групп маршрутизации и обработчиков

Когда вам нужно больше префиксов или контроллеров, вы можете установить маршрутизатор на внешнем уровне и создать глобальную группу маршрутизации.

Промежуточное программное обеспечение используется для регистрации промежуточного программного обеспечения, вы можете указать контроллер или префикс

Самым особенным является конфигурация

Конфигурация используется для регистрации «Компонентов» (Сервис/Репозиторий/Компонент)

Регистрация заводской функции, регистрация имени Bean по умолчанию, все в конфигурации

Компонент внутри конфигурации называется «PrimeBean».

Всего контейнер загрузит бин дважды.

Первый раз был PrimeBean

Второй раз это NormalBean

Логика загрузки PrimeBean такова

Если нет загруженного Bean того же типа с таким же именем, загрузить его, если он уже есть, Crash

Логика загрузки NormalBean такова

Если нет загруженного bean-компонента того же типа, загрузите bean-компонент этого типа по умолчанию; если есть и только один, пропустите его; если их несколько, вы должны указать имя для загрузки

-- О АОП

Я не рассматривал возможность написания модулей АОП в краткосрочной перспективе, в большинстве случаев Middleware может решить проблему.

Синтаксис АОП все еще может использовать набор пружин.Если есть лучший дизайн, добро пожаловать в пр/выпуск

--- Суммировать

Проект только стартовал и еще не достиг минимальной доступности

Эта статья является рекламой, а не официальным документом (официального документа пока нет)

Если вы заинтересованы в этом проекте или у вас есть комментарии/предложения по дизайну всего проекта

Добро пожаловать на Github для поднятия пр/вопроса

Следующему коммиту, возможно, придется подождать, пока я сдам последний экзамен (escape