[Project] 서버 프레임워크 구조( MVC구조 )

soohee·2022년 2월 12일

project

목록 보기
6/7

혼자 프로젝트를 했을 때, 가장 신경을 못썼던 서버 구조에 대해 이번 프로젝트를 통해 많이 알게 되었다. 많은 협업자 분들과 룰을 정해서, 그 룰에 맞춰 개발을 하고, 설계를 도우는 그런 것들에 대해 정말 많이 배워서 내가 아는 선에서 최대한 남겨보고 싶었다.

📕 framework의 개념

프레임워크는 어떠한 목적을 달성하기 위해 복잡하게 얽혀있는 문제를 해결하기 위한 구조며, 소프트웨어 개발에 있어 하나의 뼈대 역할을 하는 것이다. 주로 라이브러리와 헷갈리는 경우가 많은데, 쉽게 프레임 워크 = 클래스 + 라이브러리라고 생각하면 된다. 프레임워크가 더 상위단계 개념이다. 특정 프로그램을 개발하기 위한 여러 요소들과 메뉴얼인 룰을 제공하는 프로그램으로써 프레임워크는 사용자에게 재사용성이 높은 툴인 셈이다.

📒 MVC 구조

이번에 다룰 내용은 웹 API 서버 MVC구조이다.
MVC구조는 Domain - Repository - Service - Controller 로 이루어져있다.
( 좀 다른 구조도 있는 것 같은데 큰 틀은 비슷해 보인다. )
이런식으로 나누는 이유는 추상화를 위해서이지 않나 싶다. 각각 계층이 다르고, 그 계층 간의 추상화가 잘되어있으면, 의존도가 낮아 바꾸기도 편리하다.

1. Domain

도메인은 비지니스 도메인 객체가 들어있는 곳이다. 개발 당시에는 쉽게 db 테이블이 들어가는 곳이라고 생각했다. ( domain의 객체들로 db를 automigrate 시켰기 때문이다.)
아래와 같은, struct를 만들어 놓고 사용하였다. 아무래도 db 테이블의 내용과 일맥상통하다보니, primaryKey를 지정해준다던가, default 값을 선언해 두기도 했다.

type Customer struct {
	Id                 uint 	 	`gorm:"primaryKey"`
	Email              string
	Password           string
	PhoneNumber        string
	LoginType          uint
	CreatedAt          time.Time 	`gorm:"autoCreateTime"`
	UpdatedAt          time.Time 	`gorm:"autoUpdateTime"`
	DeletedAt          gorm.DeletedAt
}

2. Repository

DB와 직결되는 곳이다. 이곳에서 쿼리문을 작성하고, 데이터를 어떻게 다룰지를 메소드로 만들어두면 된다. 단순 DB와 관계있는 부분만 있어야 된다.

func (repo *Repository) CreateCustomer(cu c.Customer) (c.Customer, error) {
	err := repo.db.Create(&cu).Error

	if err != nil {
		return c.Customer{}, err)
	}
	return cu, nil
}

3. Service

비즈니스 로직이 담긴 메서드와 메서드명을 선택해야 한다. 주로 하나의 역할을 하도록 개발되며, 여러가지의 역할을 할 경우, 트랙잭션 체크등을 하여, 하나의 흐름을 타고 수행되도록 작성해야 한다. (rollback등을 이용)
여러가지 역할을 한다는 의미는, 여러가지 repo를 부르는 것이다. repo는 단순 db작업에 쓰이는 메서드이기 때문에, 하나의 일만 수행한다.
( 여기서 하나의 역할과 하나의 일의 차이가 헷갈릴 수 있는데, 하나의 역할이 중복확인이라면, 하나의 일은, id을 가지고, 이메일를 가져오기, 이메일로 중복확인을 수행하기 이렇게 두가지가 될 수 있다. )

func (ser *Service) CheckDuplicate(id) (bool, error) {
	u, err := ser.GetEmail(id)
    
    if err != nil {
		return false, err
	}

	isDuplicated := func(Email string) bool {
		return len(Email) != 0
	}

	return isDuplicated(u.Email), nil
}

4. Controller

클라이언트와 가장 가까운 곳으로써, Service를 이용해 Business Logic을 처리하는 곳이다. 직접적인 로직이 들어가진 않고, Service를 호출하는 형태로 쓰인다.
가장 밖에서부터 Controller -> Service -> Repository 순서대로 들어가게 되며, 서로가 서로의 안의 내용을 몰라야한다. 즉, Controller에서 Repository를 바로 호출해서는 안된다는 것이다. 무조건 Service를 거쳐서 불러야한다. 따라서 Controller에서는 여러 서비스를 부를 수 있기 때문에, 가장 큰 형태라고 생각하면된다.

func (co *Controller) Login(c echo.Context) error {

	params := make(map[string]string) 
    
    if err := c.Bind(&params); err != nil {
		return err
	}
    
	id := c.QueryParam("id")
    pwd := c.QueryParam("password")

	nu, err := co.Service.GetUser(id,pwd)

	if err != nil {
		return err
	}

	res := nu.user

	return c.JSON(http.StatusOK, res)
}

📗 더 나은 서버 위한 구조들

1. package

패키지는 관련 라이브러리를 가져다가 만든 메소드를 모아두는 곳이다. 주의해야 할 점은, 어디에도 갖다 붙일 수 있어야 하기 때문에, 프로젝트 내부에서만 쓰는, form이나, domain을 가져다가 쓰면 안된다.
예를 드러, aws s3 관련 메소드, iot 관련 메소드 들을 만들어 두는 곳이다.

2. form

위에서 쓰이진 않았지만, 해당 메소드들의 input, output의 form을 만들고, 그 해당 폼을 한번에 관리하는 곳이다. 개발 후반에, 설계문서와 비교할때 parameter와 response값을 한번에 체크할 수 있어 유용하다.

3. config

환경변수등을 모아놓을 때 쓰는 곳이다. 어떠한 라이브러리를 사용하거나, 외부 api를 사용할 때, 환경변수를 바꿔야하는데 이때, 상수로 저장하지말고, 따로 변수화 시켜서 정리해 놓는다면, 다른 협업자가 사용하기 편리하다.

🐾 한발짝 성장일기

하나의 구조를 파악해놓으니, 다른 구조와 차이점을 구분하기도 쉬울것이라는 생각이 들었다. 또한, 중구난방으로 개발하기 보다 어떠한 틀이 생기니까, 메소드간 역할 구분하기도 쉽고, 서로 추상화가 되어있어 보안성도 좋고, 의존도도 줄일 수 있어 매우 편리했다.

profile
🐻‍❄️

0개의 댓글