📌 비즈니스로직이 뭘까?
우테코 프리코스 과제였던 로또게임을 예로 들어보자.
당첨번호와 입력번호를 받아서 당첨내역과 수익률을 출력하는 게임이었다.
한줄로 설명할 수 있는 간단한 게임이지만
'1~45 사이의 숫자 입력'
'1000원 단위 숫자 입력'
'5개 번호 일치 + 보너스번호 일치시 보너스당첨'
등 요구사항들을 모두 접목하다 보면 복잡해진다.
로또 게임에서의 입력을 버튼으로 가정하고 로직을 작성해 본다면 아래와 같다.
1. 구매금액 입력버튼
- 1000원 단위인지 숫자인지 유효성 검사를 한다.
- 로또 구매 가능 개수를 계산한다.
- '3장의 로또를 구매했습니다.' 와 같은 안내 문구를 출력한다.
- 구매 개수에 따라 당첨 번호를 만들어서 출력한다.
2. 사용자 당첨번호 입력버튼
- 6개의 숫자로 이루어진 1에서 45사이의 숫자인지 유효성 검사를 한다.
- 당첨 번호와 비교해서 일치 정도에 따라 당첨여부를 계산한다.
- 수익률을 계산한다.
- 당첨내역과 수익률의 데이터를 받아 출력한다.
사용자는 단순히 숫자를 입력해서 버튼을 클릭할 뿐이지만
그 뒤에는 여러가지 데이터와 연산기능들이 연결되어있다.
이런 요구사항들을 도메인/비즈니스 로직이라 불리며
이로인해 버튼은 단순한 클릭요소 이상의 의미를 가지게 된다.
💡 비즈니스로직과 UI의 분리
1. 추상화
위 로또 과제는 3주차 과제였는데
2주차까지는 일정한 패턴 없이 코드를 작성하다가
로또과제에서는
데이터들을 관리하는 Model,
가공된 로직을 보여주는 View,
Model과 View를 연결하는 Controller 패턴을 가진
MVC 패턴으로 구현했다.
이런식으로 구현했더니 장점이
게임의 흐름을 관장하는 Controller에서
비즈니스로직 처리(수익률계산)와 데이터의 출력 과정(콤마와 % 기호 붙이기)을 알 필요가 없으니
코드가 훨씬 간단해지고 읽기 간단해졌다.
코드 수정이 있을 때에도
출력 부분의 형식이 문제일 경우 view에서
계산값이 이상할 경우 model에서 수정하면 되므로
유지보수또한 쉬워졌다.
이렇게 내부 복잡한 로직들을 숨기고 필요한 부분만 적절하게 드러내는것을 추상화라고 한다.
추상화를 통해 비즈니스 로직과 UI를 분리해서 가독성과 유지보수성을 높일 수 있다.
2. 캡슐화
알약을 먹는 자는 알약 내부가 어떻게 생겼는지 알 수 없고 알지 않아도 된다.
이를 프로그래밍 관점에서 봤을 때 객체의 내부를 외부로부터 숨겨서(캡슐화)
객체의 상태를 보호하고 안전하게 유지할 수 있다.
객체의 외부에서는 객체의 중요한 특성과 기능만을 접근할 수 있도록 설계하여
코드의 안정성과 유지보수성을 높일 수 있다.
위 로또게임에서 사용자의 입력을 문자열로 받아서
유효성 검사를 거친 뒤
오름차순으로 정렬된 배열로 출력해야하는 기능이 있었다.
class Lotto {
#numbers;
constructor(numbers) {
this.#numbers = this.#sortNumbers(numbers);
Validator.validateUserWinningNumber(this.#numbers);
}
#sortNumbers(numbers) {
return numbers.sort((a, b) => a - b);
}
getNumbers() {
return [...this.#numbers];
}
}
사용자의 입력을 받아 Lotto 클래스를 생성한다.
Lotto클래스에서는 내부 정렬로직이 #sortNumbers() 메서드로 캡슐화 되어있다.
외부에서는 이 내부로직에 대한 세부사항을 알 수 없다.
내부 필드인 #numbers에 접근하려면
외부에서는 필드에 간접적으로 정보를 반환하는 getNumbers() 메서드를 통해 접근한다.
이런 설계로 외부에서는 중요한 정보에 간접적으로만 접근할 수 있고
클래스 내부 구현 세부사항을 숨기며
클래스 외부에서는 공개된 메서드만을 통해 상호작용할 수 있다.
비즈니스 로직을 숨기고 가공된 데이터만를 받아 UI로 출력할 수 있다.
2-1. 응집도
// tanstackQuery
const { data } = useQuery(key,queryFn,{
onSuccess:{},
onError:{}
});
//useEffect + useState
const [data, setData] = useState();
useEffect(() => {
fetch()
.then()
.catch()
}, [])
useEffect로
fetch로 데이터 패칭을 받아서
json을 자바스크립트 객체로 변환하고
useState로 상태를 저장하는 방법과 달리
tanstackQuery는 이러한 일련의 과정들을 숨기고
useQeury 함수 하나가 모든 기능들을 가지고 있어 응집도가 높다.
강한 응집도는 하나의 목적을 가지도록 해서 재사용성과 유지보수성을 높일 수 있다.
3. 어댑터
이부분은 이해가 어려워 지피티와 싸웠다.
해외여행 가서 전압 호환을 위해 어댑터를 사용하듯이
어댑터 패턴은 호환성이 없는 인터페이스 사이를 연결하는 다리 같은 존재이며
기존코드와 새로운 코드를 연결해서 호환성을 높일 수 있는 디자인 패턴이다.
예를 들어 customHook 내부에서 어떤 구현이 들어있든
해당 hook을 사용하는 컴포넌트는 리턴값만 잘 받으면 된다.
만약 customHook 내부에서 사용중이던 라이브러리 서비스가 종료되었을 때
다른 라이브러리로 마이그래이션을 쉽게 하기 위해서 어댑터를 사용할 수 있다.
즉 customHook에서 라이브러리를 사용한 1,2,3의 기능이 있다면
각각을 customHook1,2,3으로 감싸서
유지보수시 각각의 customHook을 하나씩 고치면 된다는 뜻이다.
비용이 더 드는 일이 아닐지 생각해볼 일이다...?
여러 customHook을 유연하게 사용하기 위해서 각각의 hook에 어댑터 패턴을 도입하여
내부 구현 간의 결합도를 낮추고, 변경에 대한 영향을 최소화 할 수 있게 된다.
👀마무리
이 포스팅을 쓰기까지 한 일주일 걸린것 같다.
이해하기 어려운 추상화의 세계를 그래도 나름 이해해보려
꾸준히 예시도 찾아보고 내가 쓴 코드도 뒤져가면서 노력한것에 의의를 두겠습니다...
📚 Ref
💠 어댑터(Adaptor) 패턴 - 완벽 마스터하기
Adaptor Pattern 어댑터 패턴(Adaptor Pattern) 이란 이름 그대로 클래스를 어댑터로서 사용되는 구조 패턴이다. 어댑터는 우리 주변에도 많이 볼 수 있는 것으로서, 대표적으로 110V 전용 가전제품에 220V
inpa.tistory.com
프론트엔드에서 비즈니스 로직과 뷰 로직 분리하기 (feat. MVI 아키텍쳐)
오늘 해볼 이야기는 상태 관리, 비즈니스와 뷰 로직의 분리 프론트엔드 개발의 구조 등 프론트엔드의 아키텍쳐에 대한 이야기입니다. 프론트엔드를 하다보면 많이들 물어보는 (저 역시 지금도
velog.io
'원티드' 카테고리의 다른 글
[12월 프리온보딩] 데이터/계산/액션 분리하기 (1) | 2023.12.13 |
---|