Some time ago I wrote a project with Kotlin — spring-boot

The development experience was great and overturned my “Java stuff” stereotype

The core ideas of Spring are DI and AOP

What would it look like to implement it in Go

Here’s an example (see Godoc Rhapsody for full documentation)

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()
}
Copy the code

Note: The Web module of this project is based on
echo )

Example: register a prefix = API /v1 routing group, and then register a controller with path = :id under this routing group

Controller acts as a tag, the equivalent of @Controller() in Spring, as does GET


You can also mark in the previous layer, such as

type UserController struct {
	Controller 	
	GET 		`path:":id" method:"GetUserInfo"`
}

type Root struct {
	*UserController `prefix:"api/v1"`
}
Copy the code

or

type UserController struct {	
	GET 		`path:":id" method:"GetUserInfo"`
}

type Root struct {
	*UserController `type:"controller" prefix:"api/v1"`
}
Copy the code

(Note: if both layers have annotations, the higher layer (Root) overwrites the comments of the lower layer (UserController).)

This doesn’t seem like much of an advantage, because dependency injection doesn’t work very well

What if we write an extension to ORM

Let’s say I want to write an extension of GORM called GormConfig

And then I just need to

type Root struct { *UserController `prefix:"api/v1"` *GormConfig *Entities } type Entities struct { *User *Score // Your  other models ... }Copy the code

You can

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
}
Copy the code

So how do YOU initialize the DB, how do you configure the database connection?

The configuration is of course written in the configuration file

The default configuration file path is “. / resources/application. Conf. “”

The default type is JSON

If you want to use yaml, simply change the file suffix to.yaml or.yml

What about custom paths?

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

That will do

Note: Convention precedes configuration and configuration precedes convention. Config. json in the above code will be interpreted as YAML


So minimize the configuration as much as possible

So once we read in the configuration file, how do we get the value we want?

For example, the configuration file says:

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

So how do we write our GORM extension?

We just need to

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 }Copy the code

Note: Why do we use Pointers for config file string injection?


Because spring-Boot is too slow to start at once, dependency analysis, loading, and injection are required


So Rhapsody will support hot updates to profiles, making it easier to use Pointers (what, to be completely hot more? Let me look into that
qlangCheck again.)

So what’s the *Application here?

This is the value returned by CreateApplication, a pointer to the global container

The entire application is loaded and assembled in this container

You specify any valid Bean (labeled Configuration/Service/ Repository/Component/ Controller/Middlware/Router/Parameter) A public member of type *Application will be injected automatically.

Similarly, GetDB above is invalid because *gorm.DB is not a valid Bean

We need an agent

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 } }Copy the code

Now that we’ve covered the basics, let’s take a deeper look at how to specify injected beans.

For example, I registered two * gorm.db in 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 } }Copy the code

In fact, beans have their own names

Whereas the Bean name produced by the factory function (method) above defaults to the function (method) name, the directly registered Bean name is the fully qualified name of the class.

When there are more than one Bean, the injected Field needs to specify the Bean name, such as

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

If we also want to specify the default *UserRepository

We need to use fully qualified names

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

To specify it

We can change its name, too

Register in Config

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

And then you can

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

In depth: Classification of beans

As mentioned above, beans are divided into

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

Configuration, Controller, Router, and Middleware can be registered directly with Root

Controller is used to register routing groups and handlers

When more prefix or Controller is needed, you can set another Router in the outer layer and create a global routing group

Middleware is used to register Middleware, specifying either Controller or prefix

The most special one is Configuration

Configuration registers “Components” (Service/ Repository/Component)

The factory function registration, Bean default name registration, is in Configuration

The beans inside the Configuration are called “primeBeans”

The container loads the Bean twice

The first was primebeans

The second is the NormalBean

The loading logic of a PrimeBean is

If there is no loaded Bean of the same type with the same name, load; If yes, Crash

The load logic of normalBeans is

If there is no loaded Bean of the same type, load the default Bean of that type. If only one exists, skip; If there is more than one, you must specify a name to load

– about AOP

Without thinking about writing AOP modules in the short term, Middleware will take care of the problem in most cases

AOP syntax can still use the same set of Spring, if there is a better design, please mention PR/issue

– summary

The project is in its infancy and has not yet achieved minimum availability

This article is a generalization, not a formal document (not yet)

If you are interested in this project, or have comments/suggestions on the whole project design

Welcome to Github to mention PR/issue

The next Commit may come after I finish my final exam