용어 설명

배달의민족앱은 배달음식점을 앱을 통해 주문하는 것인데, 배민라이더스 는 그동안 배달을 하지 않던 맛집(?)을 앱으로 주문할 수 있는 서비스입니다. 앱을 주문하는 사용자의 앱을 사용하는 방법은 모두 같지만, 배민라이더스는 배달을 직접 수행합니다. BROS 1.0는 배달의민족에서 주문이 들어오면 해당 주문을 배달 센터에 연계하여 배달 라이더분들이 업소로 음식을 픽업하여 주문 고객에게 전달하게 하는 서비스입니다. 모든 배달 건을 관리하는 관제 Web기능과 라이더전용앱이 주 서비스입니다.

왜 2.0을 하죠?

기존 1.0은 서비스 출시 시기가 무척 중요했던 시절이라 최대한 빠른 개발으로 서비스를 런칭하는것이 큰 목표였습니다. 이제 서비스의 질적 양적 확장이 중요지고 배달 플랫폼으로써 고도화된 기능이 더욱 필요하게 되었습니다. 배달의민족 주문 기능과 배달 기능을 완전 분리해서 음식 배달 본연의 역할에 집중된 배달 플랫폼이 필요하게 되었습니다.

Winter is coming

하지만 봄부터 시작한 프로젝트는 여러가지 사정(정말 어려가지)으로 프로젝트는 난항이 계속 되었습니다. 다시 배민라이더스에 집중하자는 결론이 났고 이제 다시 프로젝트를 진행을 하면 되지만, 세상에 없던 서비스가 아닌 1.0이후 2.0을 새로 개발하는것입니다. 이게 뭐가 문제냐면 플랫폼이 교체되면 사용하시는 분을 모두 재교육해야 합니다. 배달업은 날씨에 영향이 많습니다. 추워지거나 비가오면 배달량이 정말 급격히 올라갑니다. 모두다 업무에 바쁜데 새 플랫폼을 반길일이 없겠죠. 입장 바꿔서 생각해보면 개발자 한창 오픈준비할때 개발툴 바꾸라는것과도 비슷하니(아 이 적절한 비유) 그래서 12월 전에 오픈이 또한 목표가 되었습니다. 젠장

요구사항을 수집하여 상위 기획과 상세 기획을 하며 개발을 하면 됩니다. 쉽죠?

명시적으로 애자일을 표방한것도 아닙니다. 처음부터 개발 꺼리가 있던것이 아니라서 처음에는 상위기획을 기획자분과 같이 진행하였고, 스프린트마다 개발할 부분을 정해서 어쨌거나 스프린트(2주단위)를 돌렸습니다. 개발자가 기획에도 참여를 했기 때문에 제품에 대한 이해도가 평소보다는 좋았지만, 기능에 대한 변경이 매우 자주 발생했습니다. 어떻게 하나요. 이런 문제를 해결하는게 서비스 개발자의 사명인걸.

어쨌든 BROS 2.0은 아래와 같이 구성되어 있습니다.

기술 블로그이니 기술을 적어보죠

기술 구성

  • 개발언어 Java 1.8
  • 서버 개발 프래임워크 Spring Boot 1.4.1(Spring Framework 4.3.3)
  • Data 처리 Spring Data JPA, QueryDsl
  • 단위테스트 JUnit
  • API 문서 Swagger

서브모듈 구성

  • 배달라이더 api
  • 배달관제 api
  • 주문,업소 api
  • 주소 api
  • 라이더스 주문 요청 api
  • service components

5개의 api server를 구성하였고, 구현된 service componets를 사용하는 형태로 api server를 구현하였습니다. 주문,업소 api는 현재 사용하고 있는 데이타를 api서버로 구성해서 bros 2.0과는 별개로 작동하게 구성하였습니다. 그리고 주소 DB는 새로 구축해서 사용했습니다.(juso.go.kr 데이타 사용)

Data 구성

  • 주문,업소 Data
  • 주소 Data
  • BROS 2.0 Data

제품을 만들면서 가장 고마웠던 3가지 기술

확정된 기능이 아니라 잦은 구현 변경에 도움이 되었던 기술들을 꼽아보았습니다. 서비스 개발자는 정말 기능 변경이나 기능 추가에 유연한 개발을 해야 합니다. 모두 작동된다 해도 똑같은 제품이 아니라고 생각하는 뜻으로 아래 3가지 기술을 나열해봅니다.

QueryDSL

데이터를 요구사항에 맞게 표현을 하기 위해서는 여러 Entity를 조합해서 처리를 해야하는데, JPA자체만 쓰게 되면 Entity 구성에 따른 쿼리 튜닝을 별도로 하기에 너무 척박한 환경이라, 원활한 데이타 조회를 위해 QueryDSL을 사용하게 되었습니다.

@Override
public List<Delivery> findByAgencyIdAndBusinessDayAndSearchRequest(Long agencyId, String businessDay, DailyDeliverySearchRequest form) {
    return from(delivery)
            .join(delivery.deliverableAgencies, agency).on(agency.id.eq(agencyId))
            ......
            .where(delivery.id.businessDay.eq(businessDay)
                    .and(delivery.deliveryRider.agency.agencyId.eq(agencyId).or(delivery.deliveryRider.agency.agencyId.isNull()))
                    .and(buildSearchPredicate(agencyId, form)))
            .orderBy(resolveOrder(form.getOrdering()))
            .fetch();
}

개발중 Entity가 수정이 되고, QueryDSL queryType을 재생성 하게되면 Java타입이 맞지 않아 강력한 빨간 라인으로 쿼리가 깨짐을 알수 있습니다. 그리고 JPA를 사용하게 되면 LAZY 처리를 잘못해서 N+1쿼리를 자주 접하게 되는데, JPQL fetch join을 선언하게 되면 내부적으로 join문이 작동해서 필요한 데이터를 쿼리 한번에 가져올수 있습니다.

Swagger

개발중 api 문서는 별도로 관리하지 않고, 개발중인 Source를 이용해서 API 문서와 작동을 확인할 수 있는 Swagger를 사용하여, 문서작업에 대한 비용을 제거했습니다. 특정 api마다 적절한 Annotation을 선언하게 되면, Swagger 설정이 적용된 서버에서 api 문서를 제공해줍니다.

@ApiOperation(value = "배달가능 라이더 목록 조회 API ,주문상세 > 라이더 배차/변경 마우스 오버  해당 지점의 라이더 리스팅")
@RequestMapping(value = "/{sn}/{bd}/deliverable-riders", method = RequestMethod.GET)
public List<RiderAccountResponse> findDeliverableRiders(@PathVariable("sn") String sn, @PathVariable("bd") String bd) {
    return riderFindOfDeliveryService.findDeliverableRidersOfDelivery(DeliveryId.of(sn, bd));
}

Swagger JPG

JUnit

실제 연관관계를 모두 끊고 테스트해야 할 부분만 집중해서 만들기란 어렵고, 복잡한 도메인일수록 더욱더 어렵습니다. TDD는 왜 쉬운 도메인만 하는지 모르겠어요. 하지만. 그럼에도 불구하고 테스트코드가 있다면 합격. 기능이 변경이 될 때, 예기치 못했던 부분이 깨지고 버그를 발견했다면 진짜 합격. JUnit

정말 힘들었던 것

서버 개발자이자 프로젝트 PM을 했습니다. 지나고 나서는 PM의 역할보다는 개발자의 역할이 좀 더 강했고 그로 인해 프로젝트 관리를 많이 놓쳤습니다. 일정 산정도 중요하지만, 가용 자산으로 현재 어떻게 지내왔고 앞으로 어떻게 진행될 거라는 예상을 어느 정도는 해야 했고, 또한 개발자마다 진행 상황을 자세히 알아야 했고, 상호 연동을 잘할 수 있도록 판을 깔아 줘야 했지만. 못했습니다. 그래서 사단이 생겼습니다.

문제를 좀더 빨리 파악을 했더라면 다른 선택지가 있었을텐데, 개발일이라면 자신이 있었지만, 프로젝트 관리 잘못하는 PM덕분에 많은 사람이 고생을 했습니다. 머리숙여 사과. 하지만 보상은 없습니다. 알아서들 셀프 보상 하세요.

시간을 되돌릴수 있다면

  • 개발기간을 3달로 정한다.
  • 상위 기획은 종료하고 개발을 시작한다.

여러 사람이 모여서 팀을 이룰 때 모두 다 같은 생각을 가지는 것은 사실 매우 힘든 일입니다. 개개인의 모든 가용가산을 끌어다 쓴다는 것은 큰 부채를 지는 것과 같습니다.이미 미래의 자산을 끌어 쓴 샘이죠. 이럴 때 만약 프로젝트가 실패로 접어든다면 해당팀은 정말 다시 일어나기 힘듭니다. 엄청난 위험 부담을 지게 되는 것이죠.

이렇게될뻔 이렇게 되지는 않았어요!!!

그렇지만 오픈을 하고 말았어요!!

  • 8,9월 개발기간(기획포함)
  • 10월 QA 를 빙자한 개발기간
  • 11월 QA 를 빙자한 교육기간 - QA분들 정말 죄송합니다! 죽을 죄를 졌어요
  • 11월 16일 용산 오픈
  • 12월 1일 전체 오픈

QA를 시작하고 정확히 3주 후부터 제품이 안정기에 진입했습니다. 잘 돌아가지도 않던 기능을 QA 하시느라 사경을 헤매신 QA 분들 정말 죄송합니다. QA 기간을 절반 줄이고 개발 기간을 좀 더 늘렸다면 어땠을까 후회가 됩니다.

급 마무리

오픈을하다

정말 이런 메일을 받아 볼 수는 있을까? 너무나 큰 걱정을 가지고 임했던 프로젝트는 과정은 성공적이지 못했지만, 결과는 성공했습니다. 매 순간 잘못된 선택으로 고생한 사람들에게 심심한 위로를 드려요. 다음 기회가 있을지는 모르겠지만 좀 더 잘해볼게요.