Java/Spring, Spring Boot

DAO에서 CRUD의 반환 타입은 어떤 것이 적절할까?

열심히 사는 우진 2023. 4. 30. 10:36
반응형

 

데이터베이스에 대해 CRUD를 수행하고,
그 결과로 무엇을 반환해야 할까요?

 

결론부터 말하면 정답은 없다 입니다.
너무나도 열려 있는 문제이기에, 검색을 통해서도 전반적인 경향을 파악하기도 어렵습니다.

 

하지만 상황에 따라 더 나은 해답은 있을 수 있기에,
그것에 대해 개인적으로 고민한 결과를 공유해보고자 합니다.

 


 

개요

데이터베이스에 접근하는 DAO(Data Access Object)가 구현되어 있고,
다음의 3가지 반환타입을 가질 수 있다고 가정하겠습니다.

  • 데이터베이스 테이블에 1:1로 매핑되는 Entity
  • Entityid
  • void
    이외에도 작업이 성공했음을 나타내는 boolean, 영향을 준 row의 개수를 나타내는 값 등을 가질 수도 있지만,
    문제를 단순화하기 위해 고려하지 않겠습니다. (그것이 필요한 정보라면 반환해야할 것입니다.)

반환 타입이 고민되는 가장 큰 지점은 어떤 정보가 필요한가?이고,
그중에서도 변경된 정보가 필요한가? 입니다.

 

예를 들면, 어떤 정보를 수정했을 때
수정한 정보를 다시 반환해줄 필요가 있는지에 대한 문제입니다.

 

이것을 기준으로 CRUD 각각에 대한 견해를 작성해보겠습니다.

 

 

Read

  • Entity / List<Entity> 반환

Read(SQL의 SELECT)의 경우는 딱히 고민의 여지가 없습니다.

조회한 정보를 Entity(혹은 Entity의 컬렉션, 예를 들면 List<Entity>)로 반환하면 됩니다.

 

Delete

  • void 반환
  • idEntity도 가능

Delete(SQL의 DELETE)는 대개 아무 정보를 반환하지 않습니다.

데이터가 삭제되었기 때문에, 다시 활용되지 않는 것이 자연스럽다고 생각합니다.

하지만 삭제한 정보가 필요한 경우에는, id가 아닌Entity를 반환할 필요가 있습니다.

 

예를 들어 id를 입력 받아, 해당하는 정보를 삭제하는 경우를 생각해보겠습니다.

삭제한 정보를 사용자에게 보여줘야 한다면, id만으로는 다시 정보를 조회할 수 없습니다. 정보가 DB에서 삭제되었기 때문입니다.

따라서 삭제한 정보가 필요한 경우에는 Entity를 반환해야 합니다.

 

Create

  • id / Entity 반환
  • void 불가능

Create(SQL의 INSERT)는 void를 반환해서는 안 됩니다.

왜냐하면, void를 반환하고 생성한 정보가 사용자에게 필요한 경우가 생긴다면

다시 찾을 수가 없기 때문입니다.

 

(DB에서 id 부여한다고 가정하면) 생성 당시에는 id가 없었을 것이기에,

Create를 호출한 곳에는 id를 알지 못합니다.

따라서 Create는 생성한 정보(DB 테이블의 Row)의 id를 반환해야 합니다.

 

필요한 경우에는 Entity를 반환할 수도 있습니다.

이 경우, 생성한 정보의 id를 가지고 SELECT 쿼리를 한 번 더 실행해야 합니다.

 

Create하는 많은 경우가, 생성한 정보를 즉시 필요로 한다면

INSERTSELECT를 다시 실행하더라도 Entity를 반환하는 것이 유리할 수 있습니다.

(id를 통한 단건 조회라면, 데이터베이스의 인덱싱에 의해 속도가 굉장히 빠릅니다.)

 

Update

  • id / Entity / void 가능

Update는 모든 경우가 가능합니다.

 

우선 void를 반환해도 무방합니다.

id를 통해 정보를 업데이트한다면, 필요할 때 id로 해당 정보를 다시 조회할 수 있기 때문입니다.

(id가 아니라 특정 조건이라고 해도 마찬가지입니다.)

 

Create와 마찬가지로, 필요한 경우에 idEntity를 반환할 수 있습니다.

 

 

보다 나은 방식을 위해 반드시 고려할 사항

위에서는 CRUD에서 가능한 반환타입에 대해 고민해보았는데요,

그럼에도 CRUD 각각의 반환타입을 정하는 것은 쉽지 않습니다.

 

하지만 아래의 고려 사항이 좋은 힌트가 될 수 있습니다.

통일성

Createid를 반환하는데 Updatevoid 또는 Entity를 반환하는 경우,

DAO를 사용하는 사람(다른 개발자를 가정) 입장에서 무척 헷갈릴 수 있을 것 같습니다.

 

이것이 팀 내부에서 사용되는 단순 클래스가 아닌 오픈소스 라이브러리의 클래스라면,

사용하는 사람 입장에서 더욱 헷갈릴 것입니다. 그렇다고 물어보기도 쉽지 않습니다.

최소 놀람의 법칙을 고려하여, 통일성을 고민하는 것이 필요합니다.

 

Createid를 반환한다면, Updateid를 반환하고,

CreateEntity를 반환한다면, UpdateEntity를 반환하는 것이

보다 직관적인 구조라고 생각합니다.

 

비즈니스적 관점

해당 프로그램이 해결하는 비즈니스 문제의 특성을 고려해야 합니다.

만약 해당 비즈니스에서 정보의 삽입, 수정이 일어날 때마다 즉시 사용자에게 정보를 보여줘야 한다면,

그때마다 Entity를 반환하는 것이 좋을 수도 있습니다.

 

코드로 예시를 들어보겠습니다.

// Create가 id만 반환하는 경우
class OrderService {
        ...

        public Order createOrder(int price) {
                long id = this.orderDao.create(price);
                OrderEntity = this.orderDao.findById(id);
                ... // Order 생성
                return order;
        }

                public Order createFreeOrder() {
                long id = this.orderDao.create(0);
                OrderEntity = this.orderDao.findById(id);
                ... // Order 생성
                return order;
        }
}

위처럼 Service에서 DAOCreate, Update를 호출하는 메서드의 대부분이 Entity를 필요로 한다면,

굳이 DAO의 메서드를 2번 호출하기보다 create() 메서드가 OrderEntity를 반환하는 것이 나을 수 있습니다.

 

// Create가 Entity를 반환하는 경우
class OrderService {
        ...

        public Order createOrder(int price) {
                OrderEntity = this.orderDao.create(price);
                ... // Order 생성
                return order;
        }

                public Order createFreeOrder() {
                OrderEntity = this.orderDao.create(0);
                ... // Order 생성
                return order;
        }
}

(보수 받는 개발자 경력이 없는지라, 가정이 많이 부족할 수 있습니다.)

 

그런데 CREATE 또는 UPDATE한 정보를 다시 반환하는 것이, 비용이 많이 드는 일일 수도 있습니다.

 

아래에 의사코드로 예시를 들겠습니다.

class OrderDao {
        ...

        public List<OrderEntity> updateIfPriceOverThousand(Data data) {
                database.update("UPDATE order SET price data = data WHERE price > 1000");
                List<OrderEntity> orderEntities = database.query("SELECT * FROM order WHERE price > 1000");
                return orderEntities;
        }
}

위의 코드에서는, DAO에서 UpdateEntityList를 반환합니다.

그런데, price가 1,000 이상인 데이터가 수백만 건이라면,

게다가 해당 정보가 반드시 필요한 것이 아니라면,

필요하지 않은 경우 SELECT를 실행하지 않는 것만으로 서버 리소스가 크게 감소할 수 있을 것입니다.

(더 나은 비즈니스적 가정이 있다면 추천 부탁드립니다!)

 


 

역시나 정답은 없는 것 같습니다.

하지만 그때그때의 필요만 고민하기보다는

지금의 코드가 쌓여 나중에 어떤 코드가 될지 고려하면서 구현한다면,

미래의 나와 동료 개발자들이 고민할 시간을 아껴줄 수 있다는 생각이 듭니다.

 

지적과 의견을 환영합니다.

감사합니다!

 

 

 

 

 

 

반응형