アプリケーションサービスとは
アプリケーションサービスとは、ユースケースを実現する処理のことを言います。ドメインオブジェクトを組み合わせてユースケースを実現します。MVC では controller、クリーンアーキテクチャでは usecase を指します。
ユースケースを組み立てる
ドメインオブジェクトから準備する
domain/model/user.go
package model
import (
"fmt"
"github.com/google/uuid"
)
type User struct {
ID string
Name string
Address string
}
type UserCreateConfig struct {
Name string
Address string
}
type UserUpdateConfig struct {
Name string
Address string
}
func NewUser(conf UserCreateConfig) (*User, error) {
u := &User{
ID: uuid.NewString(),
Address: conf.Address,
}
err := u.ChangeName(conf.Name)
if err != nil {
return nil, err
}
return u, nil
}
func (m *User) Update(conf UserUpdateConfig) error {
if conf.Name == "" {
return fmt.Errorf("name is required")
}
if conf.Address == "" {
return fmt.Errorf("address is required")
}
m.Name = conf.Name
m.Address = conf.Address
return nil
}
func (m *User) ChangeName(name string) error {
if name == "" {
return fmt.Errorf("ユーザ名は必須です。")
}
if len(name) < 3 {
return fmt.Errorf("ユーザ名は3文字以上です。%s", name)
}
m.Name = name
return nil
}
domain/repository/repository.go
package repository
import "github.com/taisa831/go-ddd/domain/model"
type Repository interface {
FindUserByName(string) (*model.User, error)
FindUserByID(string) (*model.User, error)
FindUsers() ([]*model.User, error)
CreateUser(*model.User) error
UpdateUser(*model.User) error
DeleteUser(*model.User) error
}`
domain/service/user_service.go
package service
type UserService interface {
Exists(name string) (bool, error)
}
リポジトリのインターフェースを実装する
infrastructure/repository/user_repository.go
package repository
import (
"github.com/taisa831/go-ddd/domain/model"
)
type User struct {
ID string
Name string
Address string
}
func (r *dbRepository) FindUserByName(name string) (*model.User, error) {
var user User
if err := r.db.Where("name = ?", name).First(&user).Error; err != nil {
return nil, err
}
return r.convertToUserModel(&user), nil
}
func (r *dbRepository) FindUserByID(id string) (*model.User, error) {
var user User
err := r.db.Where("id = ?", id).First(&user).Error
if err != nil {
return nil, err
}
return r.convertToUserModel(&user), nil
}
func (r *dbRepository) CreateUser(user *model.User) error {
return r.db.Create(r.convertToUserRecord(user)).Error
}
func (r *dbRepository) FindUsers() ([]*model.User, error) {
users := []*User{}
if err := r.db.Find(&users).Error; err != nil {
return nil, err
}
return r.convertToUserModels(users), nil
}
func (r *dbRepository) UpdateUser(user *model.User) error {
return r.db.Model(User{}).Where("id = ?", user.ID).Updates(User{
Name: user.Name,
Address: user.Address,
}).Error
}
func (r *dbRepository) DeleteUser(user *model.User) error {
return r.db.Model(User{}).Where("id = ?", user.ID).Delete(user).Error
}
func (r *dbRepository) convertToUserRecord(user *model.User) *User {
return &User{
ID: user.ID,
Name: user.Name,
Address: user.Address,
}
}
func (r dbRepository) convertToUserModel(user *User) *model.User {
return &model.User{
ID: user.ID,
Name: user.Name,
Address: user.Address,
}
}
func (r dbRepository) convertToUserModels(users []*User) []*model.User {
userModels := make([]*model.User, len(users))
for i, u := range users {
userModels[i] = &model.User{
ID: u.ID,
Name: u.Name,
Address: u.Address,
}
}
return userModels
}
ユーザ情報更新処理を作成する
application/usecase/user_usecase.go
func (u *UserUsecase) Update(userID string, req request.UserUpdateRequest) error {
duplicated, err := u.us.Exists(req.Name)
if err != nil {
return err
}
if duplicated {
return &model.UserExistsError{}
}
user, err := u.r.FindUserByID(userID)
if err != nil {
return err
}
conf := model.UserUpdateConfig{
Name: req.Name,
Address: req.Address,
}
if err := user.Update(conf); err != nil {
return err
}
if err := u.r.UpdateUser(user); err != nil {
return err
}
return nil
}
退会処理を作成する
application/usecase/user_usecase.go
func (u *UserUsecase) Delete(userID string) error {
user, err := u.r.FindUserByID(userID)
if err != nil {
return err
}
if err := u.r.DeleteUser(user); err != nil {
return err
}
return nil
}
ドメインルールをアプリケーションサービスに記述しない
アプリケーションサービスはあくまでもドメインオブジェクトのタスク調整に徹するべきで、アプリケーションサービスにはドメインのルールは記述されるべきではありません。もしもドメインのルールをアプリケーションサービスに記述してしまうと、同じようなコードを点在させることに繋がります。
まとめ
- アプリケーションサービスはドメインオブジェクトの操作に徹することでユースケースを実現する
- アプリケーションサービスにドメインのルールが記述されないように気をつける