Study. Clean Architecture 20장 업무 규칙 DEV / SEED
2022-09-20 posted by sang12
에플리케이션을 업무 규칙과 플러그인으로 구분하려면 업무규칙이 실제로 무엇인지를 잘 이해해야만 한다.
업무 규칙
사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차이다.
컴퓨터상으로 구현했는지와 상관없이, 업무 규칙은 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 한다. 심지어 사람이 수동으로 직접 수행하더라도 마찬가지이다.
Ex) 대출에 N%의 이자를 부과한다는 사실은 은행이 돈을 버는 업무 규칙이다 (이러한 사실은 컴퓨터 프로그램으로 이자를 계산하든, 또는 직원이 주판을 튕겨 계산하든 하등의 관계가 없다)
→ “이러한 규칙을 핵심 업무 규칙 (Critical Business Rule) 이라고 부르겠다!“
왜냐하면 이들의 규칙은 사업 자체에 핵심적이며, 규칙을 자동화하는 시스템이 없더라도 업무 규칙은 그대로 존재하기 때문이다!
핵심 업무 규칙(Critical Business Rule)은 보통 데이터를 요구한다.
EX) 대출에는 대출잔액, 이자율, 지급일정이 필요하다. (심지어 이런 데이터는 시스템 자동화가 되지 않은 경우에도 존재하는 그런 데이터이다)
→ “이러한 데이터를 핵심 업무 데이터 (Critical Business Data) 라고 부르겠다!“
“핵심 규칙과 핵심 데이터는 본직적으로 결합되어 있기 때문에 객체로 만들 좋은 후보가 된다. 우리는 이러한 유형의 객체를 엔티티(Entity) 라고 하겠다“
엔티티
컴퓨터 시스템 내부의 객체로서, 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화한다.
엔티티의 인터페이스는 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구현한 함수들로 구성된다.
엔티티는 데이터베이스, 사용자 인터페이스, 서드파티 프레임워크에 대한 고려사항들로 인해 오염되어서는 절대 안 된다
(순수한 업무 규칙을 가지고 있는 존재! 즉 도메인과 JPA의 의존성을 예로 생각해 볼 수 있을거 같다.)
해당 클래스(Loan)는 어떤 시스템에서도 업무를 수행할 수 있으며, 시스템의 표현방식이나 데이터 저장 방식, 해당 시스템에서 컴퓨터가 배치되는 방식과도 무관하다. 순전히 업무에 대한 것이며, 이외의 것은 없다
(업무에 대한 것이며, 이외의 것은 없다!)
꼭 객체지향 언어를 사용할 필요는 없다. 유일한 요구조건은 핵심 업무 데이터와 핵심 업무 규칙을 하나로 묶어서 별도의 소프트웨어 모듈로 만들어야 한다는 것이다.
유스케이스
모든 업무 규칙이 엔티티처럼 순수한 것은 아니다. 자동화된 시스템이 동작 하는 방법을 정의하고 제약함으로써 수익을 얻거나 비용을 줄이는 업무 규칙도 존재한다.
“은행 직원이 신규 대출을 생성할 때 사용하는 애플리케이션을 상상해보자. 은행에서 대출 담당자가 신청자의 신상정보를 수집하여 검증한 후, 신청자의 신용도가 500보다 낮다면 대출 견적을 제공하지 않기로 결정했다고 해 보자. 따라서 시스템에서 신상정보 화면을 모두 채우고 검증한 후, 신용도가 하한선보다 높은지가 확인된 이후에 대출 견적 화면으로 진행 되어야 한다는 식으로 은행에서 업무 요건을 기술했다고 해보자“
→ 이것이 바로 유스케이스이다
유스케이스는 자동화된 시스템이 사용 되는 방법을 설명한다.
유스케이스는 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 그리고 해당 출력을 생성하기 위한 처리 단계를 기술한다.
엔티티 내의 핵심 업무 규칙과는 반대로, 유스케이스는 애플리케이션에 특화된(application-specific) 업무 규칙을 설명한다.
엔티티 내부의 핵심 업무 규칙을 어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다.
-> 엔티티가 어떻게 춤을 출지를 유스케이스가 제어하는 것이다.시스템이 사용자에게 어떻게 보이는지를 설명하지 않는다.
애플리케이션에 특화된 규칙을 설명하며, 이를 통해 사용자와 엔티티 사이의 상호작용을 규정한다.
유스케이스는 객체다. 애플리케이션에 특화된 업무 규칙을 구현하는 하나 이상의 함수를 제공한다.
엔티티와 유스케이스
엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 알지 못한다.
엔티티와 같은 고수준 개념은 유스케이스와 같은 저수준 개념에 대해 아무것도 알지 못한다. 반대로 저수준인 유스케이스는 고수준인 엔티티에 대해 알고 있다.
왜 엔티티는 고수준이며 유스케이스는 저수준 일까?
→ 유스케이스는 단일 애플리케이션에 특화되어 있으며, 시스템의 출력에 보다 가깝게 위치하고 있기 때문이다.
→ 엔티티는 수많은 다양한 애플리케이션에 사용될 수 있도록 일반화된 것이므로, 각 시스템의 입력이나 출력에서 더 멀리 떨어져 있다.
유스케이스는 엔티티에 의존한다. 반면 유스케이스에 의존하지 않는다.
요청 및 응답 모델
유스케이스는 입력 데이터를 받아서 출력 데이터를 생성한다. 그런데 제대로 구성된 유스케이스 객체라면 데이터를 사용자나 또 다른 컴포넌트와 주고 받는 방식에 대해서는 전혀 눈치챌 수 없어야 한다. 우리는 유스케이스 클래스의 코드가 HTML이나 SQL에 대해 알게 되는 일을 절대로 원치 않는다.
유스케이스는 단순한 요청 데이터 구조를 입력으로 받아들이고, 단순한 응답 데이터 구조를 출력으로 반환한다.
이들 데이터 구조는 어떤 것에도 의존하지 않는다.
이들 데이터 구조는 HttpRequest나 HttpResponse 같은 표준 프레임워크 인터페이스로부터 파생되지 않는다.
웹에 대해서도 알지 못한다
웹 뿐만 아니라 사용자 인터페이스에도 종속되는게 아무것도 없다.
요청 및 응답 모델이 독립적이지 않다면, 그모델에 의존하는 유스케이스도 결국 해당 모델이 수반하는 의존성에 간접적으로 결합되어 버린다.
엔티티 객체를 가리키는 참조를 요청 및 응답 데이터 구조에 포함하려는 유혹을 받을 수도 있다.
(도메인 객체를 응답값으로 사용 하는 것을 말하는 걸까? 그런거 같다.)
하지만 이 유혹을 떨쳐내라! 이들 두 객체의 목적은 완전히 다르다. 시간이 지나면 두 객체는 완전히 다른 이유로 변경될 것이고, 공통 폐쇄 원칙과 단일 책임 원칙을 위배하게 된다.
결론
업무 규칙은 소프트웨어 시스템이 존재하는 이유다.
업무 규칙은 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오염되어서는 안 되며, 원래 그대로의 모습으로 남아 있어야 한다.
“이상적으로는 업무 규칙을 표현하는 코드는 반드시 시스템의 심장부에 위치 해야 하며, 덜 중요한 코드는 이 심장부에 플러그인되어야한다. 업무 규칙은 시스템에서 가장 독립적이며 가장 많이 재사용할 수 있는 코드여야 한다.“
(업무 규칙을 표현하는 코드는 심장부에 위치해야한다. 레이어드나 헥사고날 클린아키텍처 또한 이런 개념들을 지키기 위한 아키텍쳐인거 같다. 하지만 여기에서 독립적이며 가장 많이 재사용할 수 있는 코드여야 한다는 말은.. 많은 곳에서 사용되야 한다? 라는 말인가? 인프라스트럭처에서도 도메인을 건들여도 되나..? 어렵다!)
출처 : 클린 아키텍처 - 소프트웨어 구조와 설계의 원칙
쓰레기 줍기 리펙터링 DEV / SEED
2021-12-01 posted by sang12
"코드를 파악하던 중에 일을 비효율적으로 처리하는 모습을 발견할 때가 있다."
"리팩토링 해야할 곳이 보이는데.. 하려던 작업과 관련 없는 일에 너무 많은 시간을 빼앗기긴 싫을 때"
"간단한 수정은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모를 남긴 다음, 하던 일을 끝내고 나서 처리한다"
"캠핑 규칙이 제안하듯, 항상 처음 봤을 때보다 깔끔하게 정리하고 떠나자"
출처: 리팩터링 2판 저자:마틴 파울러 아저씨... 재밌는 아저씨네
spring 개발자와 Express의 만남(회고) DEV / SEED
2021-10-25 posted by sang12
회사에서 백엔드 서버를 처음부터 끝까지 구축해야 하는 상황이 있어서 Spring, Express 서버를 고민하다 Express 서버로 Restful Api 서버를 구현을 한 적이 있다. Express 서버를 선택한 이유는 프로젝트가 그렇게 크지 않았고, 관련된 서비스의 백엔드도 Express로 설계되어있어서 참고하여 개발하면 크게 어려울 거 같지 않아서였다. (Spring 환경에서 개발하던 지겨움도 한몫 했던 거 같다)
현재 서비스는 무사히 운영 중에 있지만, 내가 만든 구조가 그게 최선일까? 라는 생각을 할때 가 많았다.
서버 구조를 간략히 설명하자면 express rotuer에서 get/post 형태의 url로 접근이 되면 xxx-controller.js쪽으로 넘겨주게 되고 해당 controller에서는 xxx-service.js 모듈로 넘겨주고 service에서는 최종 xxx-model(dao)를 호출하여 비지니스 로직을 수행하고 리턴해주는 형태의 서비스였다.
나름 예쁜 프로젝트를 만들고 싶어서 Spring에서 사용하는 디렉토리(아키텍쳐) 구조를 따라갔고, Swagger를(완전 노가다지만..) 이용하여 해당 Resful Api를 호출하는 업체에게 제공하기도 했었다. 보안은 Jwt토큰 쿼리빌더는 knex을 사용했고 배포는 jenkins에서 pm2를 이용하여 처리 하였다.
그래 그때는 그게 최선이였고 현재도 Express서버는 정상 가동 중이다. 하지만 머릿속에 이게 최선이였을까라는 생각이 메아리처럼 울렸고 어떤 부분이 부족했을까라는 고민을 하게 되었다.
일단 개발하며 느낀 Express의 장점은 투박함? 이였다. Spring에서는 기본적으로 제공해주고 간단한 설정을 하면 할 수 있는 것들이 Express에서는 제공이 안되서, 제공해주는 기능들을 찾거나 thirdParty libray를 이용해야 했다. transaction이랄지... anotation이랄지... vo나 obejct는 어떻게 활용하지 @valid는 없나? 와 같은 것들.. 그와 동시에 Spring에서 이런 것들(개발자의 고민, 철학)이 녹아져 만들어진 프레임워크 안에서 개발하는데도.. 나는 아무것도 모르고 개발만 했구나라는 창피함을 느끼기도 했다.
Express의 장점은 투박함에서 나오는 가벼움 같다. 항상 Spring을 이용해 개발할 때면 testCode를 작성하지 않거나 하면 local에서 tomcat 서버를 수십 번 내리고 올리는 모습을 볼 수 있다. 하지만 Express서버는 무진장 가볍다. 그래서 MSA 아키텍처에 많이 사용하지 않을까? (물론 모름) 이란 상상을 해봤다.
그럼 본론으로 Express 서버로 프로젝트를 구현했을 때 나의 부족한 부분을 생각한다면 크게 3가지의 문제가 있었다고 생각이 든다.
1. 테스트코드의 부재
2. 소스간의 종속성
3. 도메인, Vo 객체의 부재
테스트 코드를 왜 만들지 않았을까? 사실 여태 테스트코드에 대해 내가 개발을 쉽게 하기 위해서라고 생각했던 게 큰 것 같다. 물론 내가 개발을 안정적으로 더욱 빠르게 하려는 것도 맞겠지만, 해당 프로젝트에 오류가 없는지? 다른 사람들이 내가 개발한 것을 수정 했을 때 어떻게 검증하면 되는지? 와 같은 생각들이 빠져 있었던 것 같다. (사실 Express와 Swagger를 사용하니 Express는 가볍고 Swagger호출 해서 콘솔로 찍고 확인하고 개발을 하며.... 테스트 코드 만들생각을 안...) 그러던 어느날 프로젝트가 끝마칠 무렵 다른 Express 서버를 수정해야 하는 이슈가 생겼었는데 하나의 로직에 Callback 형태로 많은 서비스로직들이 물고물고 물려있었고... 몇 시간의 삽질 끝에 수정한 기억이 있다.. 테스트코드라도 있었으면... 아니 콜백형태가 아니였더라면... 아니 심지어 주석이라도 있었으면.... 이렇게 테스트 코드에 대해서 생각하던 중 자연스럽게 2번의 소스 간의 종속성 생각을 안 할 수가 없었다.
종속성.. 이부분은 nodejs에서 require이 싱글톤이라는 생각에 뭐 이정도면 됐지 하고 넘어간 부분이다. 내가 가져온 객체가 싱글톤으로 관리되고 있으니 호출될때마다 여러 객체가 생기고 죽고 반복하지 않으니 좋구나~ 하고 넘어갔던 거 같다. 하지만 여기서 생각하지 못했던 부분은 해당 객체를 생성하는 주체가 해당 객체를 사용하는 객체라는 점 이였다. 뭐 흔하지는 않겠지만 있을 수도있는 것이 특정 종속되는 dao를 통째로 변경해야 된다거나 커넥션을 받아와야 하는 dao에서 어떤 경우에는 mariadb쪽을 어떤 경우에는 oracle쪽을 조회 해야 하는 경우가 생길 수 있다. 그렇다면 이 구조에서는 커넥션을 가져와야하는 소스의 모든 부분을 다 변경해줘야하는 일이 발생하게 된다. 생각만해도 끔찍하다.. 하지만 사실... 이런 부분은 경험해본 적이 없어서 내게 크게 와 닿지는 않았다. 그보다 내게 와 닿았던 부분은 di로 해당 부분을 개선하면 테스트 코드를 더욱 효율적으로 만들 수 있겠구나라는 부분 정도였다. 테스트를 할 때 의존성 주입을 함으로 써, 사실 사용하는 객체 입장에서는 똥이든 된장이든 내가원하는 것만 받아오면 크게 상관할 일이 없다. 그럼으로 내가 테스트 해야할 부분에 해당 객체가 원하는 데이터를 주입해줌으로써, 해당 부분만 손쉽게 테스트가 가능하다는 장점이 생긴다. 예를 들어 Express 서버의 서비스 로직에 회원 정보를 가져와 카카오메세지를 보내는 기능이 존재한다고 생각해보자. 그럼 서비스 로직에서는 회원 정보를 가져오는 부분이 테스트 범위에 포함될까? 물론 큰 관점에서의 테스트는 해당 부분도 포함이 되겠지만 일단 서비스로직 관점에서 보면 db가죽었건 살았건 회원 정보만 가져오면 된다. 그리고 카카오 메세지 보내는 부분도 메세지가 보내졌다는 리턴만 받으면 되지 카카오메세지 서버가 죽었거나 실제로 카카오메세지를 보내는 등의 테스트까지는 서비스로직의 관점에서는 필요하지 않을 것이다. 그러니 프로젝트간 종속성을 낮춘 상태에서는 서비스 로직이 원하는 객체를 moking을 통하여 전달하면, 디비가 죽었건 메세지 서버가 죽은 것과 상관 없이 서비스 로직이란 단위로부터의 테스트가 가능하다는 장점이 생긴다. 이게 di를 통한 이점이 아닐까...
물론 위의 생각들 중에 틀린 말도 많을 거 같다. 하나의 프로젝트를 마치고나서 흘러갔던 생각들과 그걸로 인해서 공부하게 됐던 부분들을 정리해 봤다. 3번 도메인, Vo 객체의 부재는 글을 적으면서 생각이 났다... 현재 업무를 하면서 사용하고 있는 Spring 어드민페이지는 모든 데이터들이 Map key value값으로 사용한다. 그러다 보니 어떤 데이터가 들어오는지를 대체 알 수가 없다. 기존에 개발되어있던 부분에서 수정을 하다가 어디선가 사용하고 있는 변수를 사용한다면..? Validation 체크를 하는데 이런값이 어디서 들어오지? 안쓰는거아냐 하고 지웠다면? 이런 부분에 있어 수정이 쉽지 않다. 아니 수정하려면 앞 단에 있는 jsp에서 어떤 값을 던지는지 그리고 그게 jsp파일 1개인지 여러 개이면 여러 개의 값들을 다 확인 해야 한다... 그러다 보니 그냥 내버려두게 된다. 테스트도 쉽지 않다. 이런 생각을 몸소 체험하고 있었는데도 Express에서는 지원되는 라이브러리나 그런 것들이 있는지 확인해보지도 않았다. 회고라고 적고 반성문을 적는 기분이다 :)