DAO에서 CRUD의 반환 타입은 어떤 것이 적절할까?
데이터베이스에 대해 CRUD를 수행하고,
그 결과로 무엇을 반환해야 할까요?
결론부터 말하면 정답은 없다
입니다.
너무나도 열려 있는 문제이기에, 검색을 통해서도 전반적인 경향을 파악하기도 어렵습니다.
하지만 상황에 따라 더 나은 해답은 있을 수 있기에,
그것에 대해 개인적으로 고민한 결과를 공유해보고자 합니다.
개요
데이터베이스에 접근하는 DAO(Data Access Object)
가 구현되어 있고,
다음의 3가지 반환타입을 가질 수 있다고 가정하겠습니다.
- 데이터베이스 테이블에 1:1로 매핑되는
Entity
Entity
의id
void
이외에도 작업이 성공했음을 나타내는boolean
, 영향을 준row
의 개수를 나타내는 값 등을 가질 수도 있지만,
문제를 단순화하기 위해 고려하지 않겠습니다. (그것이 필요한 정보라면 반환해야할 것입니다.)
반환 타입이 고민되는 가장 큰 지점은 어떤 정보가 필요한가?
이고,
그중에서도 변경된 정보가 필요한가?
입니다.
예를 들면, 어떤 정보를 수정했을 때
수정한 정보를 다시 반환해줄 필요가 있는지에 대한 문제입니다.
이것을 기준으로 CRUD 각각에 대한 견해를 작성해보겠습니다.
Read
Entity
/List<Entity>
반환
Read
(SQL의 SELECT)의 경우는 딱히 고민의 여지가 없습니다.
조회한 정보를 Entity
(혹은 Entity
의 컬렉션, 예를 들면 List<Entity>
)로 반환하면 됩니다.
Delete
void
반환id
나Entity
도 가능
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
하는 많은 경우가, 생성한 정보를 즉시 필요로 한다면
INSERT
후 SELECT
를 다시 실행하더라도 Entity
를 반환하는 것이 유리할 수 있습니다.
(id
를 통한 단건 조회라면, 데이터베이스의 인덱싱에 의해 속도가 굉장히 빠릅니다.)
Update
id
/Entity
/void
가능
Update
는 모든 경우가 가능합니다.
우선 void
를 반환해도 무방합니다.
id
를 통해 정보를 업데이트한다면, 필요할 때 id
로 해당 정보를 다시 조회할 수 있기 때문입니다.
(id
가 아니라 특정 조건이라고 해도 마찬가지입니다.)
Create
와 마찬가지로, 필요한 경우에 id
나 Entity
를 반환할 수 있습니다.
보다 나은 방식을 위해 반드시 고려할 사항
위에서는 CRUD에서 가능한 반환타입에 대해 고민해보았는데요,
그럼에도 CRUD 각각의 반환타입을 정하는 것은 쉽지 않습니다.
하지만 아래의 고려 사항이 좋은 힌트가 될 수 있습니다.
통일성
Create
가 id
를 반환하는데 Update
가 void
또는 Entity
를 반환하는 경우,
DAO
를 사용하는 사람(다른 개발자를 가정) 입장에서 무척 헷갈릴 수 있을 것 같습니다.
이것이 팀 내부에서 사용되는 단순 클래스가 아닌 오픈소스 라이브러리
의 클래스라면,
사용하는 사람 입장에서 더욱 헷갈릴 것입니다. 그렇다고 물어보기도 쉽지 않습니다.
최소 놀람의 법칙을 고려하여, 통일성을 고민하는 것이 필요합니다.
Create
가 id
를 반환한다면, Update
도 id
를 반환하고,
Create
가 Entity
를 반환한다면, Update
도 Entity
를 반환하는 것이
보다 직관적인 구조라고 생각합니다.
비즈니스적 관점
해당 프로그램이 해결하는 비즈니스 문제의 특성을 고려해야 합니다.
만약 해당 비즈니스에서 정보의 삽입, 수정이 일어날 때마다 즉시 사용자에게 정보를 보여줘야 한다면,
그때마다 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
에서 DAO
의 Create
, 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
에서 Update
후 Entity
의 List
를 반환합니다.
그런데, price
가 1,000 이상인 데이터가 수백만 건이라면,
게다가 해당 정보가 반드시 필요한 것이 아니라면,
필요하지 않은 경우 SELECT
를 실행하지 않는 것만으로 서버 리소스가 크게 감소할 수 있을 것입니다.
(더 나은 비즈니스적 가정이 있다면 추천 부탁드립니다!)
역시나 정답은 없는 것 같습니다.
하지만 그때그때의 필요만 고민하기보다는
지금의 코드가 쌓여 나중에 어떤 코드가 될지 고려하면서 구현한다면,
미래의 나와 동료 개발자들이 고민할 시간을 아껴줄 수 있다는 생각이 듭니다.
지적과 의견을 환영합니다.
감사합니다!