У меня возникли некоторые проблемы, когда речь заходит о дизайне модели, в частности, обработка Действия для конкретной модели против Действия с базой данных .Хорошим примером может служить моя модель User.
При создании пользователя в моей БД я хочу:
- Проверить, что пароль соответствует критериям (действие модели)
- Создание дайджеста (действие модели)
- Установка меток времени (действие модели)
- Сохранение электронной почты, дайджеста и меток времени в БД (действие БД)
При тестировании я, очевидно, хочу иметь набор модульных тестов для всех 4, однако у # 4 есть вызовы к остальным 3, что-то, что я не хочу повторно тестировать, или рискую потерпеть неудачу при тесте # 4, если какой-либо из этих 3 сделает.
Я придумал создать отдельный интерфейс для ModelActions против StoreActions и, при необходимости, отправить интерфейс UserAction в действие хранилища, однако, когда я его записываю, я уже чувствую какой-то серьезный запах кода.
type User struct {
ID int `json:"id"`
Email string `json:"email"`
Password string `json:"password"`
ConfirmationPassword string `json:"confirmationPassword"`
passwordDigest string `json:"-"`
CreatedAt time.Time `json:"createdAt,omitempty"`
ModifiedAt time.Time `json:"modifiedAt,omitempty"`
}
//UserStore is the interface for all User functions that interact with the database
type UserStore interface {
GetUserByEmailAndPassword(email, password string) (User, error)
UpdatePassword(u UserAction, previousPassword, password, confirmationPassword string) error
UserExists(email string) (bool, error)
CreateUser(u UserAction) error
}
// I am going against design Principles by having GetID, GetEmail, since JSON unmarshalling needs the struct fields to be capitalized, which is already a warning sign for me
type UserAction interface {
GetID() int
GetEmail() string
Timestamps() (time.Time, time.Time)
SetID(id int)
SetTimestamps()
SetPassword(password, confirmation string)
SetDigest(digest string)
CreateDigest() (string, error)
VerifyPassword() error
ComparePassword(password string) error
}
// Example of UserActions
func (u *User) CreateDigest() (string, error) {
var digest string
if err := u.VerifyPassword(); err != nil {
return digest, err
}
passwordByte, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return digest, err
}
digest = string(passwordByte)
return digest, nil
}
func (u *User) VerifyPassword() error {
if len(u.Password) < 6 {
return &modelError{"Password", "must be at least 6 characters long"}
}
if u.Password != u.ConfirmationPassword {
return &modelError{"ConfirmationPassword", "does not match Password"}
}
return nil
}
// Example of DB Action
func (db *DB) CreateUser(ua UserAction) error {
if exists, err := db.UserExists(ua.GetEmail()); err != nil {
return err
} else if exists {
return &modelError{"Email", "already exists in the system"}
}
// set password
digest, err := ua.CreateDigest()
if err != nil {
return err
}
ua.SetDigest(digest)
ua.SetTimestamps()
createdAt, modifiedAt := ua.Timestamps()
rows, err := db.Query(`
INSERT INTO users (email, password_digest, created_at, modified_at)
VALUES ($1, $2, $3, $4)
RETURNING id
`, ua.GetEmail(), digest, createdAt, modifiedAt)
if err != nil {
return err
}
defer rows.Close()
var id int
for rows.Next() {
if err := rows.Scan(&id); err != nil {
return err
}
}
ua.SetID(id)
return nil
}
Есть ли лучший способ смоделировать эти отдельные действия, чтобы пользовательские действия можно было смоделировать при тестировании функций DB / Store?Я попытался сохранить структуру User
как часть интерфейса, например:
type UserAction {
SetTimestamps()
CreateDigest() (string, error)
VerifyPassword() error
ComparePassword(password string) error
User() *User
}
Это, однако, вызывает циклический импорт при создании макетов, а также открывает все поля, которые уже доступны, так какполя модели можно экспортировать