goのSlackアプリで設計勉強導入
はじめに
アーキテクチャの勉強がしたかったのでgoでSlackアプリ作ってみた
まず適当に実装してみる→これができなくて困る→修正
この思考の流れを書いて整理していくので備忘録的なものになるかと思います。
個人的には勉強なので間違ってるかな?とか、もっといい書き方ないかな?と思ってもまず進める!
その後修正してみて失敗してこっちの方がいいんだ!と体で覚えていくスタイルで。
参考
環境構築
メインじゃないので参考として
環境構築
- Dockerfile作る
FROM golang:1.23.5
RUN go install github.com/air-verse/air@v1.61.7
WORKDIR /go/src/app
ENV GO111MODULE=on
COPY go.mod /go/src/app/go.mod
COPY go.sum /go/src/app/go.sum
RUN go mod download
- docker-compose作る
version: "3.9"
name: chat-bot
services:
app:
container_name: chat-bot-app
env_file:
- ./app/.env.local
image: chat-bot-app
build:
context: ./app
dockerfile: Dockerfile
depends_on:
mysql:
condition: service_healthy
tty: true
ports:
- 8080:8080
volumes:
- ./app/:/go/src/app
command: ["air", "-c", "./main/.air.toml"]
networks:
- chat-bot-network
mysql:
container_name: chat-bot-mysql
image: mysql:8.0
ports:
- 3306:3306
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- ./mysql/initdb.d:/docker-entrypoint-initdb.d
environment:
TZ: "Asia/Tokyo"
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -uroot -p
networks:
- chat-bot-network
networks:
chat-bot-network:
external: true
- air
sampleほぼそのまま
変えたところはcmd
だけ
# Config file for [Air](https://github.com/air-verse/air) in TOML format
# Working directory
# . or absolute path, please note that the directories following must be under root.
root = "."
tmp_dir = "tmp"
[build]
# Array of commands to run before each build
pre_cmd = ["echo 'hello air' > pre_cmd.txt"]
# Just plain old shell command. You could use `make` as well.
cmd = "go build -o ./tmp/main ./main/main.go"
# Array of commands to run after ^C
post_cmd = ["echo 'hello air' > post_cmd.txt"]
# Binary file yields from `cmd`.
bin = "tmp/main"
# Customize binary, can setup environment variables when run your app.
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# Add additional arguments when running binary (bin/full_bin). Will run './tmp/main hello world'.
args_bin = ["hello", "world"]
# Watch these filename extensions.
include_ext = ["go", "tpl", "tmpl", "html"]
# Ignore these filename extensions or directories.
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# Watch these directories if you specified.
include_dir = []
# Watch these files.
include_file = []
# Exclude files.
exclude_file = []
# Exclude specific regular expressions.
exclude_regex = ["_test\\.go"]
# Exclude unchanged files.
exclude_unchanged = true
# Follow symlink for directories
follow_symlink = true
# This log file is placed in your tmp_dir.
log = "air.log"
# Poll files for changes instead of using fsnotify.
poll = false
# Poll interval (defaults to the minimum interval of 500ms).
poll_interval = 500 # ms
# It's not necessary to trigger build each time file changes if it's too frequent.
delay = 0 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # nanosecond
# Rerun binary or not
rerun = false
# Delay after each execution
rerun_delay = 500
[log]
# Show log time
time = false
# Only show main log (silences watcher, build, runner)
main_only = false
# silence all logs produced by air
silent = false
[color]
# Customize each part's color. If no color found, use the raw app log.
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"
[misc]
# Delete tmp directory on exit
clean_on_exit = true
[screen]
clear_on_rebuild = true
keep_scroll = true
[proxy]
# Enable live-reloading on the browser.
enabled = true
proxy_port = 8090
app_port = 8080
実装
Slackインストール処理実装
- まず書いてみる
mainではentrypointの設定とechoサーバの起動をする
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.GET("/slack/install", slack.Install)
e.Start(":8080")
}
ハンドラーでSlackインストール処理を作る
package slack
import (
"fmt"
"log"
"net/http"
"github.com/caarlos0/env/v11"
"github.com/labstack/echo/v4"
"github.com/slack-go/slack"
)
type InstallRequest struct {
Code string `query:"code"`
}
type EnvConfig struct {
Slack Slack
}
type Slack struct {
ClientID string `env:"SLACK_CLIENT_ID,notEmpty"`
ClientSecret string `env:"SLACK_CLIENT_SECRET,notEmpty"`
}
func Install(c echo.Context) error {
var cfg EnvConfig
err := env.Parse(&cfg)
if err != nil {
log.Fatal(err)
}
// クエリパラメータからcodeを取得
var params InstallRequest
err = c.Bind(¶ms)
if err != nil {
return err
}
// OAuth2認証
response, err := slack.GetOAuthV2ResponseContext(
c.Request().Context(),
http.DefaultClient,
cfg.Slack.ClientID,
cfg.Slack.ClientSecret,
params.Code,
fmt.Sprintf("https://%s/slack/install", c.Request().Host),
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
ENV=local
SLACK_CLIENT_ID=xxxxx
SLACK_CLIENT_SECRET=xxxxx
- apiの処理の中で環境変数の読み込みを書いてるので、api追加の度に同じ処理を書くことになる
じゃあ共通化したいからレシーバにしてみる
環境変数の読み込みを切り出す
package config
import (
"github.com/caarlos0/env/v11"
)
type EnvConfig struct {
Slack slack
}
type slack struct {
ClientID string `env:"SLACK_CLIENT_ID,notEmpty"`
ClientSecret string `env:"SLACK_CLIENT_SECRET,notEmpty"`
}
func New() (*EnvConfig, error) {
var cfg EnvConfig
err := env.Parse(&cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
apiから呼び出せるよう環境変数contextを作る
package context
import (
"chat-bot/internal/config"
)
type Context struct {
Config *config.EnvConfig
}
func New(config *config.EnvConfig) *Context {
return &Context{
Config: config,
}
}
Slack用のapiハンドラを切り出してインストール処理をレシーバで実装する
package slack
import (
"chat-bot/internal/context"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/slack-go/slack"
)
type SlackHandler struct {
ctx *context.Context
}
func NewSlackHandler(ctx *context.Context) *SlackHandler {
return &SlackHandler{
ctx,
}
}
func HandleSlack(e *echo.Group, ctx *context.Context) {
handler := NewSlackHandler(ctx)
g := e.Group("/slack")
g.GET("/install", handler.Install)
}
type InstallRequest struct {
Code string `query:"code"`
}
func (h *SlackHandler) Install(c echo.Context) error {
// クエリパラメータからcodeを取得
var params InstallRequest
err := c.Bind(¶ms)
if err != nil {
return err
}
// OAuth2認証
response, err := slack.GetOAuthV2ResponseContext(
c.Request().Context(),
http.DefaultClient,
h.ctx.Config.Slack.ClientID,
h.ctx.Config.Slack.ClientSecret,
params.Code,
fmt.Sprintf("https://%s/slack/install", c.Request().Host),
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
package main
import (
"chat-bot/internal/app/slack"
"chat-bot/internal/config"
"chat-bot/internal/context"
"log"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
config, err := config.New()
if err != nil {
log.Fatalln(err)
}
ctx := context.New(config)
e := echo.New()
e.Use(middleware.Logger())
g := e.Group("")
slack.HandleSlack(g, ctx)
e.Start(":8080")
}
- apiハンドラが増えるたびにmain関数が肥大化していくので切り出したい
package routers
import (
"chat-bot/internal/app/slack"
"chat-bot/internal/context"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func New(ctx *context.Context) *echo.Echo {
e := echo.New()
e.Use(middleware.Logger())
g := e.Group("")
slack.HandleSlack(g, ctx)
return e
}
package main
import (
"chat-bot/internal/app/rest/routers"
"chat-bot/internal/config"
"chat-bot/internal/context"
"log"
)
func main() {
config, err := config.New()
if err != nil {
log.Fatalln(err)
}
ctx := context.New(config)
e := routers.New(ctx)
e.Start(":8080")
}
accessTokenをDBに保存したい
- DBの接続情報を環境変数に追加する
ここに追加していくだけでapi側からも呼び出せるようになるので修正範囲が少なくてよくなってる
package config
import (
"github.com/caarlos0/env/v11"
)
type EnvConfig struct {
Slack slack
DB db
}
type slack struct {
ClientID string `env:"SLACK_CLIENT_ID,notEmpty"`
ClientSecret string `env:"SLACK_CLIENT_SECRET,notEmpty"`
}
type db struct {
User string `env:"DB_USER_NAME,notEmpty"`
Password string `env:"DB_USER_PASSWORD,notEmpty"`
Host string `env:"DB_HOST,notEmpty"`
Port string `env:"DB_PORT,notEmpty"`
Name string `env:"DB_NAME,notEmpty"`
}
func New() (*EnvConfig, error) {
var cfg EnvConfig
err := env.Parse(&cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
- DBの接続を作ってcontextに追加する(apiから呼び出せるように)
package mysql
import (
"chat-bot/internal/config"
"fmt"
"github.com/jmoiron/sqlx"
)
func Open(config *config.EnvConfig) (*sqlx.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=True&loc=Local",
config.DB.User,
config.DB.Password,
config.DB.Host,
config.DB.Port,
config.DB.Name,
)
conn, err := sqlx.Open("mysql", dsn)
if err != nil {
return nil, err
}
if err := conn.Ping(); err != nil {
return nil, err
}
return conn, nil
}
package context
import (
"chat-bot/internal/config"
"github.com/jmoiron/sqlx"
)
type Context struct {
Config *config.EnvConfig
DB *sqlx.DB
}
func New(conn *sqlx.DB, config *config.EnvConfig) *Context {
return &Context{
Config: config,
DB: conn,
}
}
package main
import (
"chat-bot/internal/app/rest/routers"
"chat-bot/internal/config"
"chat-bot/internal/context"
"chat-bot/internal/infra/mysql"
"log"
)
func main() {
config, err := config.New()
if err != nil {
log.Fatalln(err)
}
conn, err := mysql.Open(config)
if err != nil {
log.Fatalln(err)
}
ctx := context.New(conn, config)
e := routers.New(ctx)
e.Start(":8080")
}
- インストール処理にDB保存処理を追加する
package slack
import (
"chat-bot/internal/context"
"fmt"
"net/http"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/labstack/echo/v4"
"github.com/slack-go/slack"
)
type SlackHandler struct {
ctx *context.Context
}
func NewSlackHandler(ctx *context.Context) *SlackHandler {
return &SlackHandler{
ctx,
}
}
func HandleSlack(e *echo.Group, ctx *context.Context) {
handler := NewSlackHandler(ctx)
g := e.Group("/slack")
g.GET("/install", handler.Install)
}
type InstallRequest struct {
Code string `query:"code"`
}
func (h *SlackHandler) Install(c echo.Context) error {
// クエリパラメータからcodeを取得
var params InstallRequest
err := c.Bind(¶ms)
if err != nil {
return err
}
// OAuth2認証
response, err := slack.GetOAuthV2ResponseContext(
c.Request().Context(),
http.DefaultClient,
h.ctx.Config.Slack.ClientID,
h.ctx.Config.Slack.ClientSecret,
params.Code,
fmt.Sprintf("https://%s/slack/install", c.Request().Host),
)
if err != nil {
return err
}
_, err = h.ctx.DB.ExecContext(c.Request().Context(),
`INSERT INTO slack (team_id, access_token, access_token_expires_at, refresh_token) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE
access_token=?, access_token_expires_at=?, refresh_token=?`,
response.Team.ID, response.AccessToken, time.Now().Unix()+int64(response.ExpiresIn),
response.RefreshToken, response.AccessToken, time.Now().Unix()+int64(response.ExpiresIn), response.RefreshToken)
if err != nil {
return err
}
return c.JSON(http.StatusOK, nil)
}
Slackインストール処理のテスト実装
ここまででインストール処理は完成したのでテストを実装したい
まずインストール処理を整理するとパラメータ取得→OAuth認証→DB保存
パラメータは取得するのみで整形したりしないので、テストは不要そう
OAuth認証も外部APIの結果なので不要
じゃあDB保存のところだけテストできればいいかな
ただ現状のインストール処理だとOAuth認証があるせいでテストが書けないので
この部分を切り出してテストの時はモックを利用するようにする
- OAuth認証を切り出す
外部接続なのでinfraに切り出す
package slack
import (
"chat-bot/internal/config"
"context"
"net/http"
"github.com/slack-go/slack"
)
type SlackOAuth interface {
OAuth(c context.Context, code string, redirectURL string) (*slack.OAuthV2Response, error)
}
type slackOAuth struct {
config *config.EnvConfig
}
func NewSlackOAuth(config *config.EnvConfig) SlackOAuth {
return &slackOAuth{
config,
}
}
func (s *slackOAuth) OAuth(c context.Context, code string, redirectURL string) (*slack.OAuthV2Response, error) {
return slack.GetOAuthV2ResponseContext(
c,
http.DefaultClient,
s.config.Slack.ClientID,
s.config.Slack.ClientSecret,
code,
redirectURL,
)
}
- インストール処理も切り出す
usecaseとして切り出す
package usecase
import (
ctx "chat-bot/internal/context"
"chat-bot/internal/infra/slack"
"context"
"time"
)
type SlackInstall interface {
Install(c context.Context, code string, redirectURL string) error
}
type slackInstall struct {
ctx *ctx.Context
slackOAuth slack.SlackOAuth
}
func NewSlackInstall(ctx *ctx.Context) SlackInstall {
return &slackInstall{
ctx: ctx,
slackOAuth: slack.NewSlackOAuth(ctx.Config),
}
}
func (s *slackInstall) Install(c context.Context, code string, redirectURL string) error {
response, err := s.slackOAuth.OAuth(c, code, redirectURL)
if err != nil {
return err
}
_, err = s.ctx.DB.ExecContext(c,
`INSERT INTO slack (team_id, access_token, access_token_expires_at, refresh_token) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE
access_token=?, access_token_expires_at=?, refresh_token=?`,
response.Team.ID, response.AccessToken, time.Now().Unix()+int64(response.ExpiresIn),
response.RefreshToken, response.AccessToken, time.Now().Unix()+int64(response.ExpiresIn), response.RefreshToken)
if err != nil {
return err
}
return nil
}
- slackOAuthモック化したテストを書く
localと同じDB接続してるとか環境変数用のcontextの名前がわかりにくくなってるし、context作る処理も冗長なので修正したいが一旦進める
package usecase
import (
"chat-bot/internal/config"
"chat-bot/internal/infra/mysql"
"context"
"testing"
ctx "chat-bot/internal/context"
"github.com/slack-go/slack"
)
func Test_slackInstall_Install(t *testing.T) {
cfg, err := config.New()
if err != nil {
panic(err)
}
conn, err := mysql.Open(cfg)
if err != nil {
panic(err)
}
ctx := ctx.New(conn, cfg)
s := &slackInstall{
ctx: ctx,
slackOAuth: &slackOAuthTest{
config: cfg,
},
}
context := context.Background()
defer func() {
defer ctx.DB.Close()
_, err := ctx.DB.ExecContext(context, "DELETE FROM slack WHERE team_id=?", "1")
if err != nil {
panic(err)
}
}()
type args struct {
code string
}
tests := []struct {
name string
args args
}{
{
name: "slackインストール",
args: args{
code: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := s.Install(context, tt.args.code, "")
if err != nil {
t.Errorf("slackInstall.Install() error = %v", err)
}
})
}
}
type slackOAuthTest struct {
config *config.EnvConfig
}
func (s *slackOAuthTest) OAuth(c context.Context, code string, redirectURL string) (*slack.OAuthV2Response, error) {
return &slack.OAuthV2Response{
AccessToken: "AccessToken_1",
RefreshToken: "RefreshToken_1",
ExpiresIn: 5000,
Team: slack.OAuthV2ResponseTeam{
ID: "1",
Name: "tema_1",
},
}, nil
}
ORMを変更
ここでORMをGORMに変更しみて修正箇所がどれぐらいになるのか見てみる
- mysql/db.go
- context.go
- slackinstall.go
- slackinstall_test.go
の修正が必要になる
- DBの操作をしてるところ、今で言うとusecaseが増えると修正箇所が増えてしまうので修正したい
まずDB操作を切り出す
package repository
import (
"chat-bot/internal/app/slack/repository"
"context"
"github.com/jmoiron/sqlx"
)
type slackRepository struct {
db *sqlx.DB
}
func NewSlackRepository(db *sqlx.DB) repository.SlackRepository {
return &slackRepository{
db: db,
}
}
func (r *slackRepository) Store(
ctx context.Context,
teamID string,
accessToken string,
expiresAt int64,
refreshToken string,
) error {
_, err := r.db.ExecContext(ctx,
`INSERT INTO slack (team_id, access_token, access_token_expires_at, refresh_token) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE
access_token=?, access_token_expires_at=?, refresh_token=?`,
teamID, accessToken, expiresAt,
refreshToken, accessToken, expiresAt, refreshToken)
if err != nil {
return err
}
return nil
}
それをusecaseから呼び出したいが直接NewSlackRepository
してしまうと結局DBの情報をusecaseでもつことになってしまって依存度が高くなってしまうのでinterfaceを使う
package repository
import "context"
type SlackRepository interface {
Store(ctx context.Context, teamID string, accessToken string, expiresAt int64, refreshToken string) error
}
このinterfaceを通してusecaseからDB操作を呼び出す
package usecase
import (
"chat-bot/internal/app/slack/repository"
repository2 "chat-bot/internal/infra/slack/repository"
"context"
"time"
_ "github.com/go-sql-driver/mysql"
)
type SlackInstall interface {
Install(c context.Context, code string, redirectURL string) error
}
type slackInstall struct {
slackOAuth repository2.SlackOAuth
slackRepository repository.SlackRepository
}
func NewSlackInstall(slackOAuthrepository repository2.SlackOAuth, slackRepository repository.SlackRepository) SlackInstall {
return &slackInstall{
slackOAuth: slackOAuthrepository,
slackRepository: slackRepository,
}
}
func (s *slackInstall) Install(c context.Context, code string, redirectURL string) error {
response, err := s.slackOAuth.OAuth(c, code, redirectURL)
if err != nil {
return err
}
err = s.slackRepository.Store(c, response.Team.ID, response.AccessToken, time.Now().Unix()+int64(response.ExpiresIn), response.RefreshToken)
if err != nil {
return err
}
return nil
}
その際にusecaseからもNewSlackRepository
してしまうと依存度が高くなっちゃうので
routersでnewして→handler→usecaseに渡すようにする
package routers
import (
"chat-bot/internal/app/slack"
"chat-bot/internal/context"
"chat-bot/internal/infra/mysql/repository"
repository2 "chat-bot/internal/infra/slack/repository"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func New(ctx *context.Context) *echo.Echo {
e := echo.New()
e.Use(middleware.Logger())
slackRepository := repository.NewSlackRepository(ctx.DB)
slackRepository2 := repository2.NewSlackOAuth(ctx.Config)
g := e.Group("")
slack.HandleSlack(g, slackRepository2, slackRepository)
return e
}
package slack
import (
"chat-bot/internal/app/slack/repository"
"chat-bot/internal/app/slack/usecase"
repository2 "chat-bot/internal/infra/slack/repository"
"fmt"
"net/http"
"github.com/labstack/echo/v4"
)
type SlackHandler struct {
slackInstall usecase.SlackInstall
}
func NewSlackHandler(slackOAuthRepository repository2.SlackOAuth, slackRepository repository.SlackRepository) *SlackHandler {
return &SlackHandler{
slackInstall: usecase.NewSlackInstall(slackOAuthRepository, slackRepository),
}
}
func HandleSlack(e *echo.Group, slackOAuthRepository repository2.SlackOAuth, slackRepository repository.SlackRepository) {
handler := NewSlackHandler(slackOAuthRepository, slackRepository)
g := e.Group("/slack")
g.GET("/install", handler.Install)
}
type InstallRequest struct {
Code string `query:"code"`
}
func (h *SlackHandler) Install(c echo.Context) error {
// クエリパラメータからcodeを取得
var params InstallRequest
err := c.Bind(¶ms)
if err != nil {
return err
}
h.slackInstall.Install(c.Request().Context(), params.Code, fmt.Sprintf("https://%s/slack/install", c.Request().Host))
return c.JSON(http.StatusOK, nil)
}
- これで処理を切り分けれたのでテストも修正していく
まずusecase slackinstallのテストとしては認証、DB保存の各処理の結果はどうでもよくて
認証が成功していれば、Store処理を呼べることだけ確認すればいいのでモック使ってテストできる
本来はビジネスロジックが入ってくるはずなのでそこを確認するテストになればいい
package usecase
import (
"context"
"testing"
"github.com/slack-go/slack"
)
func Test_slackInstall_Install(t *testing.T) {
repo := NewSlackInstall(&slackOAuthTest{}, &slackRepository{})
context := context.Background()
type args struct {
code string
}
tests := []struct {
name string
args args
}{
{
name: "slackインストール成功",
args: args{
code: "code123456789",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := repo.Install(context, tt.args.code, "")
if err != nil {
t.Errorf("slackInstall.Install() error = %v", err)
}
})
}
}
type slackRepository struct {
}
func (r *slackRepository) Store(
ctx context.Context,
teamID string,
accessToken string,
expiresAt int64,
refreshToken string,
) error {
return nil
}
type slackOAuthTest struct {
}
func (s *slackOAuthTest) OAuth(c context.Context, code string, redirectURL string) (*slack.OAuthV2Response, error) {
return &slack.OAuthV2Response{
AccessToken: "AccessToken_1",
RefreshToken: "RefreshToken_1",
ExpiresIn: 5000,
Team: slack.OAuthV2ResponseTeam{
ID: "1",
Name: "tema_1",
},
}, nil
}
- ここまでできたのでGORMに変更を考えると修正対象が下記になる
- mysql/db.go
- context.go
- mysql/repository配下
そんなに変わってないように見えるが切り分けたことによってDB操作の部分だけ修正すればよくなった
テストも影響あるのでDB操作部分だけになる
ビジネスロジックとかその他に影響がない(結果を受け取ってるだけなので)からよさそう
package mysql
import (
"chat-bot/internal/config"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func Open(config *config.EnvConfig) (*gorm.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=True&loc=Local",
config.DB.User,
config.DB.Password,
config.DB.Host,
config.DB.Port,
config.DB.Name,
)
conn, err := gorm.Open(mysql.Open(dsn))
if err != nil {
return nil, err
}
return conn, nil
}
package context
import (
"chat-bot/internal/config"
"gorm.io/gorm"
)
type Context struct {
Config *config.EnvConfig
DB *gorm.DB
}
func New(conn *gorm.DB, config *config.EnvConfig) *Context {
return &Context{
Config: config,
DB: conn,
}
}
package repository
import (
"chat-bot/internal/app/slack/repository"
"context"
"gorm.io/gorm"
)
type slackRepository struct {
db *gorm.DB
}
func NewSlackRepository(db *gorm.DB) repository.SlackRepository {
return &slackRepository{
db: db,
}
}
func (r *slackRepository) Store(
ctx context.Context,
teamID string,
accessToken string,
expiresAt int64,
refreshToken string,
) error {
r.db.Exec(
`INSERT INTO slack (team_id, access_token, access_token_expires_at, refresh_token) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE
access_token=?, access_token_expires_at=?, refresh_token=?`,
teamID, accessToken, expiresAt,
refreshToken, accessToken, expiresAt, refreshToken)
return nil
}
おわりに
今回作ったslackアプリだとビジネスロジックもないしわかりにくいところも多かったが
- 疎結合になることによってテストが書きやすくなった(モックを使えることによってテストの依存度が少なくなった
- フレームワークやDBなど外部接続が必要な箇所が切り分けられたので、影響範囲が少なくできた
ここら辺が実感できたのはよかった
本当はもっと色々改善できる箇所があると思うが、実際に経験して設計って重要だし面白いなと思えたのでもっと勉強していきたいです
Discussion