Goで理解するDDD – アプリケーションサービス


アプリケーションサービスとは

アプリケーションサービスとは、ユースケースを実現する処理のことを言います。ドメインオブジェクトを組み合わせてユースケースを実現します。MVC では controller、クリーンアーキテクチャでは usecase を指します。

ユースケースを組み立てる

ドメインオブジェクトから準備する

domain/model/user.go

ackage 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
}

ドメインルールをアプリケーションサービスに記述しない

アプリケーションサービスはあくまでもドメインオブジェクトのタスク調整に徹するべきで、アプリケーションサービスにはドメインのルールは記述されるべきではありません。もしもドメインのルールをアプリケーションサービスに記述してしまうと、同じようなコードを点在させることに繋がります。

まとめ

  • アプリケーションサービスはドメインオブジェクトの操作に徹することでユースケースを実現する
  • アプリケーションサービスにドメインのルールが記述されないように気をつける

関連記事