Back/Node.js

[Nodejs, TypeScript] 도메인 로직 리팩토링

운영하고 있는 배틀 서버의 구조를 바꾸고 싶었다.

 

일을 해보면서 느끼는건,

 

기획자나 QA를 하시는 분들과 노션이나 슬렉, 구두로 소통할 때,

 

'A라는 기능에 ~~을 했을 때 ~~한 문제가 생겼다'

 

이런 느낌으로 주고 받는게 많은데,

 

저런 워딩을 나나 혹은 다른 개발자들이 일할 때

 

'아아~ 특정 API에 문제가 있겠구나',

 

판단하고 작업을 들어가는 경우가 많았다.

 

여기서 기획자나 개발자나 모두 똑같은 용어로 소통하면 좋다는 다른 회사 개발팀의 이야기를 듣고,

 

나름 개선해보기로 했다..

 

개선을 결정한 이유는 크게 2가지다.

 

1.

일단 나는 1인 개발을 쭉 해왔기에, 내가 다 만들어서 어디가 문제인지 찾아갈 수 있지만,

다른 개발자들과 협업하거나, 새로 들어올 내 후임자들이 이해하기 쉬운 구조가 좋을 것 같다는 생각이 들었기 때문이다.

 

2.

기획자나 QA하시는 다른 팀원들과도 같은 용어로 소통하기 좋은 구조라 생각했기 때문이다.

 

 

기존 구조는 간단하게 이렇다.

controller - 컨트롤러

router - api 라우팅

service - 서비스 로직

 

이걸 이렇게 나눠보기로 했다.

application / controller

application / service

router

service

db

 

application의 controller, service에는 로직이 존재하지 않는다.

바깥의 service (편의상 도메인 서비스라 하겠다)

 

controller에는 application/service를 가지고 request와 response를 하는 역할만 하고,

application/service에는 로직이 없고, 도메인 서비스에 있는 필요한 기능들만 담아 컨트롤러로 반환한다.

 

 

계약서와 관련된 로직을

contractService로 넣고

(사인, 생성, 검토, 삭제 등)

 

contract.servcie.ts

const contractService = {
  createBattleContract: async (battleContract : BattleContract) => {
	// 로직
  },

  getBattleContractCount: async (memberIdx: number) => {
    const battleContractCount = await battleContractCountFromMySql(memberIdx);
	// 로직
  },

  checkupBattleContract: async (battleInviteCode: string, memberIdx: number) => {
	// 로직
  },

  getBattleContracInfo: async (battleInviteCode: string, memberIdx: number) => {
	// 로직
  },

  signBattleContract: async (battleContract : BattleContract) => {
    const battleMainInfo = await db.selectBattleMainInfo(battleContract);
    const otherMemberInfo = await battleInfoService.getOtherMemberInfo(battleContract);
    await db.updateSignBattleContractToDatabase(battleContract);
	// 로직
  },

  deleteContract: async (battleInviteCode: string) => {
    await db.deleteBattleContractFromMySql(battleInviteCode);
    // 로직
  },
};

export = contractService;

 

여기서 만들어진 로직을

 

application/service는 도메인 서비스를 불러오고, application/controller에 반환하는 역할만 한다.

 

application/service

const getBattleContractInfoService = async (battleInviteCode: string, memberIdx: number) => {
  const checkInviteCodeFromDatabase = await inviteCodeService.checkRightInviteCode(battleInviteCode);
  if (!checkInviteCodeFromDatabase) return false;
  const battleContractInfo = await contractService.getBattleContracInfo(battleInviteCode, memberIdx);
  return battleContractInfo;
};

 

invtieCodeService, contractService 이용한다.

 

application/controller

const getBattleContractInfoController: RequestHandler = async (req, res, next) => {
  try {
    const memberInfo: MemberInfo = res.locals.member;
    const { battleInviteCode } = req.params;
    const battleContractInfo = await getBattleContractInfoService(battleInviteCode, memberInfo.member_idx);
	res.locals.responseResult = { battleContractInfo };
    return next();
  } catch (err) {
    res.locals.errorContent = err;
    return errorGlobalFilter(req, res, next);
  }
};

 

컨트롤러에선 application/servcie만 이용한다.

 

이렇게 했을 때,

 

'계약 생성에 문제가 생겼어요!' 라는 이슈를 접하게 되면,

 

contract만 찾아가서 확인하면 된다.

 

api가 아닌 비개발직군과 같은 워딩으로 소통하는게 편해졌다고 생각한다.

 

나머지를 건들 필요가 없고,

 

이미 만들어진 것들을 여기저기 쓸 수 있어서,

 

코드의 재사용성 또한 좋아졌다.

 

문제점

1. 설계를 잘해야한다.

하다 보니까, 여기 저기 이용되는 도메인 서비스가 있었는데,

이걸 특정 이슈가 생겨서 바꾸니까,

관련된 모든 로직들이 변경되어서 대참사가 한번 났다..

 

2. 도메인을 이해하고 있어야한다.

처음 개발에 들어온 사람들은,

계약서가 뭔지, 코드가 뭔지, 이런 기본적인 개념이 없으면, 오히려 api로 소통하는 것보다

더 작업하기 불편한 것 같다.

 

여러가지를 시도해보고있다.

 

아직 실력이 부족하여,

 

어떤게 옳은 방식인지, 어떻게 해야 더 효율적으로 할 수 있을지 등

 

명확한 대답을 내릴 순 없지만,

 

여러가지를 고민하고, 만든 것을 다시 더 좋은 구조로 리팩토링 하려 노력하고,

 

개발하는 것은 나름 즐거운 일인 것 같다.