はじめに
今回はGoとClean Architecutureの復習を兼ねて、ゼロからAPIを実装したときに詰まった部分を備忘のためにここに記載します。Clean Architectureならびに利用したフレームワークのbeego自体については、あまりふれないので参考文献をご覧ください。
詰まった部分
Interface層であるControllerで、構造体を作成する際にどうやってInterface(継承)をわたせばいいのかわかりませんでした。
具体的には、関数NewStatusController
に渡す引数appConfigInterface
の構造体と具体的な値です。
渡すべき引数は、作成したInterface(継承)だと思っていたのですが、Interface(継承)を渡すということに、直感的に「?」が生まれました。
package controllers
import (
"yamamon/flathand-bookmark/interfaces/inside"
"yamamon/flathand-bookmark/usecase"
)
var statusController *StatusController
type StatusController struct {
BaseController
Interactor usecase.ConfigInteractor
}
func NewStatusController(appConfigInterface inside.AppConfig) *StatusController {
return &StatusController{
Interactor: usecase.ConfigInteractor{
ConfigRepository: &inside.ConfigRepository{
AppConfig: appConfigInterface,
},
},
}
}
// @router /_me [get]
func (controller *StatusController) Get() {
appConfig := controller.Interactor.ShowAppConfig()
controller.ServeResponseJSON(appConfig)
}
appConfigInterfaceの構造体と値
appConfigInterface
の構造体については、やはりInterface(継承)でしたが、具体的な値についてはInterface(継承)を実装した構造体自身の値(ポインタ)
でした。
具体的には、関数NewAppConfig
で定義されているものです。
package inside
import "yamamon/flathand-bookmark/domain"
type AppConfig interface {
GetAppConfig() domain.AppConfig
}
package inside
import (
"os"
"yamamon/flathand-bookmark/domain"
)
var (
environment string
)
const (
LocalEnvironment = "local"
)
type AppConfig struct {
Environment string
ServiceName string
}
func NewAppConfig() *AppConfig {
return &AppConfig{
Environment: newEnvironment(),
ServiceName: newServiceName(),
}
}
func (a *AppConfig) GetAppConfig() domain.AppConfig {
config := domain.AppConfig{
Environment: a.Environment,
ServiceName: a.ServiceName,
}
return config
}
func newEnvironment() string {
environment = os.Getenv("ENVIRONMENT")
if environment == "" {
return LocalEnvironment
}
return environment
}
func newServiceName() string {
return "flathand-bookmark"
}
付録
上記では、部分的なコードしか記載しなかったので、以下にディレクトリ構成とあわせてコードを記載しておきます。誰かの助けになれば幸いです。
ディレクトリ構成
├── domain
│ └── status.go
├── infrastructure
│ └── inside
│ └── config.go
├── interfaces
│ ├── controllers
│ │ ├── base.go
│ │ └── status.go
│ └── inside
│ ├── config.go
│ └── config_repository.go
├── main.go
├── routers
│ ├── commentsRouter_interfaces_controllers.go
│ └── router.go
└── usecase
├── config_interactor.go
└── config_repository.go
コード
Entities層
package domain
type AppConfig struct {
Environment string `json:"environment"`
ServiceName string `json:"serviceName" required:"true"`
}
Frameworks & Drivers層
package inside
import (
"os"
"yamamon/flathand-bookmark/domain"
)
var (
environment string
)
const (
LocalEnvironment = "local"
)
type AppConfig struct {
Environment string
ServiceName string
}
func NewAppConfig() *AppConfig {
return &AppConfig{
Environment: newEnvironment(),
ServiceName: newServiceName(),
}
}
func (a *AppConfig) GetAppConfig() domain.AppConfig {
config := domain.AppConfig{
Environment: a.Environment,
ServiceName: a.ServiceName,
}
return config
}
func newEnvironment() string {
environment = os.Getenv("ENVIRONMENT")
if environment == "" {
return LocalEnvironment
}
return environment
}
func newServiceName() string {
return "flathand-bookmark"
}
routersに関しては、本来の役割からはframework&Device層ですが、infrastructureディレクトリとは分けています。
import (
"github.com/astaxie/beego"
"yamamon/flathand-bookmark/infrastructure/inside"
"yamamon/flathand-bookmark/interfaces/controllers"
)
func init() {
ns := beego.NewNamespace("/v1",
beego.NSNamespace("/statuses",
beego.NSInclude(
controllers.NewStatusController(inside.NewAppConfig()),
),
),
)
beego.AddNamespace(ns)
}
interface層
package controllers
import (
"fmt"
"github.com/astaxie/beego"
)
type BaseController struct {
beego.Controller
}
type Error struct {
ErrorMessage string
}
func (controller *BaseController) ServeResponseJSON(response interface{}) {
controller.Data["json"] = response
beego.Info(fmt.Sprintf("response: %+v", response))
controller.ServeJSON()
}
func (controller *BaseController) ServeErrorJSON(message string, statusCode int) {
response := Error{ErrorMessage: message}
controller.Data["json"] = response
controller.Ctx.Output.SetStatus(statusCode)
beego.Info(fmt.Sprintf("error: %+v, status: %+v", response, statusCode))
controller.ServeJSON()
}
package controllers
import (
"yamamon/flathand-bookmark/interfaces/inside"
"yamamon/flathand-bookmark/usecase"
)
var statusController *StatusController
type StatusController struct {
BaseController
Interactor usecase.ConfigInteractor
}
func NewStatusController(appConfigInterface inside.AppConfig) *StatusController {
return &StatusController{
Interactor: usecase.ConfigInteractor{
ConfigRepository: &inside.ConfigRepository{
AppConfig: appConfigInterface,
},
},
}
}
// @router /_me [get]
func (controller *StatusController) Get() {
appConfig := controller.Interactor.ShowAppConfig()
controller.ServeResponseJSON(appConfig)
}
package inside
import "yamamon/flathand-bookmark/domain"
type AppConfig interface {
GetAppConfig() domain.AppConfig
}
package inside
import (
"yamamon/flathand-bookmark/domain"
)
type ConfigRepository struct {
AppConfig
}
func (repository *ConfigRepository) ShowAppConfig() domain.AppConfig {
return repository.AppConfig.GetAppConfig()
}
usecase層
package usecase
import (
"yamamon/flathand-bookmark/domain"
)
type ConfigInteractor struct {
ConfigRepository ConfigRepository
}
func(interactor *ConfigInteractor) ShowAppConfig() domain.AppConfig {
return interactor.ConfigRepository.ShowAppConfig()
}
package usecase
import "yamamon/flathand-bookmark/domain"
type ConfigRepository interface {
ShowAppConfig() domain.AppConfig
}
その他
package main
import (
_ "yamamon/flathand-bookmark/routers"
"github.com/astaxie/beego"
)
func main() {
if beego.BConfig.RunMode == "dev" {
beego.BConfig.WebConfig.DirectoryIndex = true
beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
}
beego.Run()
}
参考文献
さいごに
これが僕の初めての投稿になります。記載内容や文章などにも多々不備があると思います。コメントいただけますと、本記事はもちろんのこと、後学のためにもなりますので、よろしくお願い致します。