Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 웹애플리케이션서버
- 오블완
- springboot
- typescript
- designpatterns
- golang
- Gin
- test
- satisfiles
- RTK
- css
- tanstackquery
- React
- Redux
- Spring
- component
- go
- JPA
- ReactHooks
- java
- Chakra
- 티스토리챌린지
- react-hook-form
- javascript
- frontend
- storybook
- hook
- backend
- JavaSpring
Archives
- Today
- Total
bkdragon's log
[gin] Clean Architecture 본문
데이터베이스에 접근하는 respository layer,
비지니스 로직을 수행하는 service layer,
요청을 해석하는 controller layer로 계층을 분리할 것이다.
우선 가장 기본 CRUD 만 있는 인터페이스를 만들고 구현체를 만든다.
type IRepository[M any, ID any] interface {
List() ([]*M, error)
Retrieve(id ID) (*M, error)
Save(entity *M) (*M, error)
Update(id ID, updates map[string]interface{}) (*M, error)
Delete(id ID) error
}
DataBase 구조체를 만들어서 IRepository 인터페이스를 구현한다.
type DataBase[M any, ID any] struct {
db *gorm.DB
}
func NewRepository[M any, ID any](db *gorm.DB) IRepository[M, ID] {
return &DataBase[M, ID]{db}
}
func (r *DataBase[M, ID]) List() ([]*M, error) {
var entities []*M
err := r.db.Find(&entities).Error
return entities, err
}
func (r *DataBase[M, ID]) Retrieve(id ID) (*M, error) {
var entity M
err := r.db.First(&entity, id).Error
return &entity, err
}
func (r *DataBase[M, ID]) Save(entity *M) (*M, error) {
err := r.db.Create(&entity).Error
return entity, err
}
func (r *DataBase[M, ID]) Update(id ID, updates map[string]interface{}) (*M, error) {
var entity M
err := r.db.Model(&entity).Where("id = ?", id).Updates(updates).Error
if err != nil {
return nil, err
}
err = r.db.First(&entity, id).Error
return &entity, err
}
func (r *DataBase[M, ID]) Delete(id ID) ( error) {
var entity M
err := r.db.Delete(&entity, id).Error
return err
}
이제 User 모델을 만들고 UserRepository를 구현한다.
type User struct {
gorm.Model
Name string `gorm:"size:100;not null"`
Age int `gorm:"not null"`
Email string `gorm:"size:100;unique;not null"`
Deleted bool `gorm:"default:false"`
CompanyID uint
Company Company
}
type IUserRepository interface {
IRepository[models.User, int]
// List() ([]*models.User, error)
// Retrieve(id int) (*models.User, error)
// Save(entity *models.User) (*models.User, error)
// Update(id int, updates map[string]interface{}) (*models.User, error)
// Delete(id int) (error)
}
임베드라고 형태인데 주석 해놓은 부분과 같은 효과이다. (상속과 비슷하다.)
type UserRepository struct {
DataBase[models.User, int]
}
임베드는 구조체, 인터페이스 두 곳다 사용할 수 있다.
서비스 레이어엔 방금 만든 userRepository를 가지고 있으면 된다.
type UserService struct {
repo repository.IUserRepository
}
func NewUserService(repo repository.IUserRepository) *UserService {
return &UserService{repo: repo}
}
func (s *UserService) ListUsers() ([]*models.User ,error) {
users, err := s.repo.List()
if err != nil {
return nil, errors.New("data Base Error")
}
if len(users) == 0 {
return []*models.User{}, nil
}
return users, nil
}
func (s *UserService) GetUser(id int)(*models.User ,error) {
user, err := s.repo.Retrieve(id)
if err != nil {
return nil, err
}
return user, nil
}
func (s *UserService) CreateUser(request models.CreateUser)(*models.User ,error) {
user := models.User{
Name : request.Name,
Email: request.Email,
Age: request.Age,
}
createdUser, err := s.repo.Save(&user)
if err != nil {
return nil, err
}
return createdUser, nil
}
func (s *UserService) UpdateUser(id int, request models.UpdateUser)(*models.User ,error) {
updateMap := make(map[string]interface{})
if request.Name != nil {
updateMap["name"] = request.Name
}
if request.Email != nil {
updateMap["email"] = request.Email
}
if request.Age != nil {
updateMap["age"] = request.Age
}
updatedUser, err := s.repo.Update(id, updateMap)
if err != nil {
return nil, err
}
return updatedUser, nil
}
func (s *UserService) DeleteUser(id int) error {
err := s.repo.Delete(id)
if err != nil {
return err
}
return nil
}
컨트롤러 레이어에선 서비스 클래스를 가지고 있고 필요한 요청 바디나 id 등을 실제 request 객체에서 얻어서 인자로 제공만 해주면 된다.
type UserController struct {
service service.UserService
}
func NewUserController(service service.UserService) *UserController{
return &UserController{service: service}
}
func (uc *UserController) ListUsers(c *gin.Context) {
result, err := uc.service.ListUsers()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
func (uc *UserController) GetUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
result, err := uc.service.GetUser(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, *result)
}
func (uc *UserController) GetUserByEmail(c *gin.Context) {
email := c.Param("email")
result, err := uc.service.GetUserByEmail(email)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, *result)
}
func (uc *UserController) CreateUser(c *gin.Context) {
var request models.CreateUser
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
result, err:= uc.service.CreateUser(request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, *result)
}
func (uc *UserController) DeleteUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
if err := uc.service.DeleteUser(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "사용자가 성공적으로 삭제되었습니다"})
}
func (uc UserController) UpdateUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "유효하지 않은 ID"})
return
}
var request models.UpdateUser
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "유효하지 않은 입력"})
return
}
result, err := uc.service.UpdateUser(id, request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, *result)
}
이렇게 기본 CRUD를 구현했다. 만약 이메일을 기준으로 유저를 찾아오는 기능을 추가하고 싶다면 어떻게 해야할까?
레포지토리 레이어 부터 추가하면 된다. IUserRepository 에 FindByEmail 메서드를 추가하고 구현해준다.
type IUserRepository interface {
IRepository[models.User, int]
FindByEmail(email string) (*models.User, error)
}
func (r *UserRepository) FindByEmail(email string) (*models.User, error) {
var user models.User
if err := r.db.Where("email = ?", email).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
서비스 레이어와 컨트롤러 레이어에도 추가해주면 된다.
// service layer
func (s *UserService) GetUserByEmail(email string)(*models.User ,error) {
user, err := s.repo.FindByEmail(email)
if err != nil {
return nil, err
}
return user, nil
}
//controller layer
func (uc *UserController) GetUserByEmail(c *gin.Context) {
email := c.Param("email")
result, err := uc.service.GetUserByEmail(email)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, *result)
}
각 계층의 역할을 알기 때문에 디버깅, 유지보수가 편해질 것 같다. 그리고 무엇보다 테스트를 작성하기가 굉장히 편해진다.
레포지토리 계층을 모킹해서 리턴 값을 제어하면 리턴 값에 따라 서비스 레이어에서 그에 맞는 결과가 잘 나오는지만 확인하면 된다. 이부분은 또 다른 글에서 다뤄보겠다.
'golang' 카테고리의 다른 글
[gin] Paging Repository (0) | 2024.09.21 |
---|---|
[gorm] 다형성 관계 (0) | 2024.09.11 |
[go] http 통신 과정 (0) | 2024.09.10 |
[gin] JSON (1) | 2024.09.08 |
[gin] ShouldBindJSON (0) | 2024.09.08 |