packagearticlesimport("context")// Repo defines the DB level interaction of articles
typeRepointerface{Get(ctxcontext.Context,idstring)(Article,error)Create(ctxcontext.Context,arArticleCreateUpdate)(string,error)}// Service defines the service level contract that other services
// outside this package can use to interact with Article resources
typeServiceinterface{Get(ctxcontext.Context,idstring)(Article,error)Create(ctxcontext.Context,arArticleCreateUpdate)(Article,error)}typearticlestruct{repoRepo}// New Service instance
funcNew(repoRepo)Service{return&article{repo}}// Get sends the request straight to the repo
func(s*article)Get(ctxcontext.Context,idstring)(Article,error){returns.repo.Get(ctx,id)}// Create passes of the created to the repo and retrieves the newly created record
func(s*article)Create(ctxcontext.Context,arArticleCreateUpdate)(Article,error){id,err:=s.repo.Create(ctx,ar)iferr!=nil{returnArticle{},err}returns.repo.Get(ctx,id)}
packagetransportimport("database/sql""net/http""github.com/gin-gonic/gin""github.com/kott/go-service-example/pkg/services/articles""github.com/kott/go-service-example/pkg/services/articles/store")typehandlerstruct{ArticleServicearticles.Service}// Activate sets all the services required for articles and registers all the endpoints with the engine.
funcActivate(router*gin.Engine,db*sql.DB){articleService:=articles.New(store.New(db))newHandler(router,articleService)}funcnewHandler(router*gin.Engine,asarticles.Service){h:=handler{ArticleService:as,}router.GET("/articles/:id",h.Get)router.POST("/articles/",h.Create)}func(h*handler)Get(c*gin.Context){...}func(h*handler)Create(c*gin.Context){...}
packageapiimport("context""fmt""github.com/gin-gonic/gin""github.com/kott/go-service-example/pkg/db"articles"github.com/kott/go-service-example/pkg/services/articles/transport""github.com/kott/go-service-example/pkg/utils/log""github.com/kott/go-service-example/pkg/utils/middleware")// Config defines what the API requires to run
typeConfigstruct{DBHoststringDBPortintDBUserstringDBPasswordstringDBNamestringAppHoststringAppPortint}// Start initializes the API server, adding the required middleware and dependent services
funcStart(cfg*Config){conn,err:=db.GetConnection(cfg.DBHost,cfg.DBPort,cfg.DBUser,cfg.DBPassword,cfg.DBName)iferr!=nil{log.Error(ctx,"unable to establish a database connection: %s",err.Error())}deferfunc(){ifconn!=nil{conn.Close()}}()router:=gin.New()router.Use(/* some middleware */)articles.Activate(router,conn)iferr:=router.Run(fmt.Sprintf("%s:%d",cfg.AppHost,cfg.AppPort));err!=nil{log.Fatal(context.Background(),err.Error())}}
有许许多多可以组织项目的方式,这个方式是我认为最好的。
根据功能将 service 分开有助于之后进行修改时定义上下文边界和代码导航。
将路由注册、业务逻辑、存储等放到同一个 service 层中,也让我们更关注业务逻辑本身和更容易地进行测试。
不要让简单的事情变复杂!如果您想节约时间,那就一定要这样做。