이 문서에서는 AuthService와 User Service의 통합 필요성에 대해 알아보고, 중복 코드 문제를 해결해보겠다. 현재 두 서비스가 각각의 역할을 수행하고 있지만, 특정 기능에서 중복이 발생하고 있어 이를 통합함으로써 코드의 효율성을 높이고 유지보수성을 개선할 수 있을 것 같다.
1. 문제점 분석
현재 AuthService는 인증 관련 기능을, UserService는 사용자 관련 기능을 담당하고 있는데 다음과 같은 중복 코드가 발생하고 있다:
- getFindByEmail()
- getUserById()
이러한 함수들은 두 서비스 모두에서 필요하게 되어, 각 서비스에 중복으로 구현되고 있는데 이는 코드의 가독성을 떨어뜨리고, 유지보수 시 불필요한 작업을 증가시킨다.
2. 통합의 필요성
서비스 통합을 통해 얻을 수 있는 이점은 다음과 같습니다:
- 중복 코드 제거: 동일한 기능을 두 번 구현할 필요가 없어진다.
- 유지보수 용이성: 코드 변경 시 한 곳만 수정하면 되므로, 오류 발생 가능성이 줄어든다.
- 코드 가독성 향상: 서비스가 명확하게 분리되지 않고 중복이 발생하는 것을 방지하여, 전체 코드 구조가 더 깔끔해진다.
entities/auth.go를 삭제하고 전부 entitiels/user.go에 Schema를 만든다.
package entities
// User struct
type User struct {
Id uint `json:"id" gorm:"primaryKey"`
Email string `gorm:"uniqueIndex;not null;type:varchar(255)" json:"email"`
Password string `gorm:"not null" json:"password"`
Username string `json:"username"`
}
type CreateUserInputSchema struct {
Email string `json:"email"`
Password string `json:"password"`
Username string `json:"username"`
}
type LoginInputSchema struct {
Email string `json:"email"`
Password string `json:"password"`
}
type SignUpInputSchema struct {
Email string `json:"email"`
Password string `json:"password"`
PasswordConfirm string `json:"password_confirm"`
Username *string `json:"username"`
}
type ChangePasswordInputSchema struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
NewPasswordConfirm string `json:"new_password_confirm"`
}
router를 수정한다.
package routes
import (
"camping-backend-with-go/api/handlers"
"camping-backend-with-go/pkg/user"
"github.com/gofiber/fiber/v2"
)
func AuthRouter(app fiber.Router, service user.Service) {
app.Post("/auth/login", handlers.Login(service))
app.Post("/auth/signup", handlers.CreateUser(service))
}
router와 연결되는 Handler도 수정해준다.
package handlers
import (
"camping-backend-with-go/api/presenter"
"camping-backend-with-go/pkg/entities"
"camping-backend-with-go/pkg/user"
"errors"
"github.com/gofiber/fiber/v2"
"net/http"
)
// CreateUser is a function to create user data to database
// @Summary Create User
// @Description Create User
// @Tags Users
// @Accept json
// @Produce json
// @Param user body entities.SignUpInputSchema true "Register user"
// @Success 200 {object} presenter.JsonResponse{data=presenter.User}
// @Failure 503 {object} presenter.JsonResponse
// @Router /user [post]
func CreateUser(service user.Service) fiber.Handler {
return func(c *fiber.Ctx) error {
var requestBody entities.SignUpInputSchema
err := c.BodyParser(&requestBody)
// json parsing
if err != nil {
jsonResponse := presenter.JsonResponse{
Status: false,
Data: nil,
Error: err.Error(),
}
return c.Status(http.StatusBadRequest).JSON(jsonResponse)
}
if requestBody.Email == "" {
c.Status(http.StatusInternalServerError)
return c.JSON(presenter.UserErrorResponse(errors.New(
"Please specify title and author",
)))
}
err = service.CreateUser(&requestBody)
if err != nil {
jsonResponse := presenter.JsonResponse{
Status: false,
Data: nil,
Error: err.Error(),
}
return c.Status(http.StatusBadRequest).JSON(jsonResponse)
}
// password와 confirm_password가 다르면 error
if requestBody.Password != requestBody.PasswordConfirm {
jsonResponse := presenter.JsonResponse{
Status: false,
Data: nil,
Error: "password didn't match",
}
return c.Status(http.StatusBadRequest).JSON(jsonResponse)
}
jsonResponse := presenter.JsonResponse{
Status: false,
Data: nil,
Error: "Welcome",
}
return c.Status(http.StatusOK).JSON(jsonResponse)
}
}
auth.Service는 없앨 예정이니 user.Service CreateUser Method를 만든다.
package user
import "camping-backend-with-go/pkg/entities"
type Service interface {
CreateUser(signUpInputSchema *entities.SignUpInputSchema) error
Login(loginInputSchema entities.LoginInputSchema) (string, error)
}
type service struct {
repository Repository
}
func NewService(r Repository) Service {
return &service{
repository: r,
}
}
func (s *service) Login(loginInputSchema entities.LoginInputSchema) (string, error) {
return s.repository.Login(loginInputSchema)
}
func (s *service) CreateUser(signUpInputSchema *entities.SignUpInputSchema) error {
return s.repository.CreateUser(signUpInputSchema)
}
마지막으로 repository부분을 수정하자.
package user
import (
"camping-backend-with-go/pkg/entities"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"strings"
)
type Repository interface {
CreateUser(signUpInputSchema *entities.SignUpInputSchema) error
hashPassword(password string) (string, error)
}
type repository struct {
DBConn *gorm.DB
}
func NewRepo(dbconn *gorm.DB) Repository {
return &repository{
DBConn: dbconn,
}
}
func (r *repository) CreateUser(signUpInputSchema *entities.SignUpInputSchema) error {
var user entities.User
// hashing password
password := signUpInputSchema.Password
email := signUpInputSchema.Email
username := signUpInputSchema.Username
hashedPassword, err := r.hashPassword(password)
if err != nil {
return err
}
user.Password = hashedPassword
user.Email = email
// username
if username == nil {
username = &strings.Split(email, "@")[0]
user.Username = *username
} else {
user.Username = *signUpInputSchema.Username
}
if err := r.DBConn.Create(user).Error; err != nil {
return err
}
return nil
}
func (r *repository) hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}