선택 과제) 본인이 작성했던 코드 중에서 비즈니스 로직을 분류 해보고 해당 로직을 적절히 캡슐화 하며 느낀점 블로그에 정리하기
다음과 같은 순서로 코드를 정리해보세요.
1. 본인이 작성한 코드에서 비즈니스 로직을 표시해 보세요.
2. 비즈니스 로직에 해당하는 코드들을 오늘 수업에서 다룬 개념들을 활용하여 UI 로직으로부터 격리해 보세요.
3. 개선 전과 후에 어떤 차이가 있었는지 설명을 덧붙여보세요.
비즈니스 로직과 UI 로직 분리 과제의 대상은 JavaScript를 배우고 처음 작업한 작은 토이프로젝트로 선정했다.
React를 사용한 것이 아니라 Vanilla.js로 구현했기 때문에 함수들을 살펴보기 좋기 때문이다.
https://github.com/yeonhub/PP-shopping-basket
GitHub - yeonhub/PP-shopping-basket: [개인 프로젝트] 쇼핑몰 장바구니
[개인 프로젝트] 쇼핑몰 장바구니. Contribute to yeonhub/PP-shopping-basket development by creating an account on GitHub.
github.com
1. 비즈니스 로직 표시
비즈니스 로직을 찾기 위해 우선 함수들이 어떤 기능을 하는지 알아보았는데 해당 프로젝트에서 선언되고 사용된 함수중 몇 가지만 확인을 해보려 한다.
// 상품 가격 변환
function priceToString(price) {
// element 재할당
function re() {
// 상품 목록에 list 출력
function show() {
// thumbnail hover 시 이미지 변환
function pic() {
// 찜
function jjim() {
// cart 상품 증가/감소
function cartPM(incBtn, redBtn) {
// remove cart item (장바구니 상품 제거)
function cartRemove(remBtn) {
// add cart (장바구니에 담기)
function add() {
// total price (총 금액)
function totalPrice() {
// order cart +/- (주문하기 장바구니 수량 증감)
function cartPMC(incBtnC, redBtnC) {
// remove order cart (주문하기 장바구니 제거)
function cartRemoveC(remBtnC) {
// check order cart (주문하기 장바구니 체크)
function chkC(chkBtnC) {
// order cart show (주문하기 장바구니 출력)
function showCart() {
위 함수들 중에 비즈니스 로직이 포함된 함수를 잘 살펴보고 비즈니스 로직과 UI 로직으로 구분해 보았다.
// 상품 가격 변환
function priceToString(price) {
string으로 변환 후 세 자리마다 ' , ' 를 찍는 함수 -> UI 로직
// element 재할당
function re() {
HTML element를 재할당 하는 함수 -> UI 로직
// 상품 목록에 list 출력
function show() {
상품 데이터로 목록을 생성 후 출력 -> UI 로직
// thumbnail hover 시 이미지 변환
function pic() {
li element에 style을 추가하는 함수 -> UI 로직
// 찜
function jjim() {
찜 상품의 id를 담은 배열 idList에 push/filter -> 비즈니스 로직
찜 아이콘 style 변경 -> UI 로직
// cart 상품 증가/감소
function cartPM(incBtn, redBtn) {
상품 데이터의 cartStock를 증감 -> 비즈니스 로직
변경된 데이터를 기반으로 출력 -> UI 로직
// remove cart item (장바구니 상품 제거)
function cartRemove(remBtn) {
상품 데이터의 cartStock와 cart 불린값을 변경 -> 비즈니스 로직
변경된 데이터를 기반으로 출력 -> UI 로직
// add cart (장바구니에 담기)
function add() {
클릭한 상품의 dataset.id를 기준으로 list 객체와 cartList 객체 수정 -> 비즈니스 로직
생성된 element를 DOM에 추가 -> UI 로직
// total price (총 금액)
function totalPrice() {
cartItems를 list에서 filter후 생성 + 총 금액을 합한 후 price.innerHTML -> UI 로직
// order cart +/- (주문하기 장바구니 수량 증감)
function cartPMC(incBtnC, redBtnC) {
list의 stock을 수정 -> 비즈니스 로직
수정된 데이터를 출력 -> UI 로직
// remove order cart (주문하기 장바구니 제거)
function cartRemoveC(remBtnC) {
list의 stock 증가와 cartList 데이터 수정 -> 비즈니스 로직
수정된 데이터를 출력 -> UI 로직
// check order cart (주문하기 장바구니 체크)
function chkC(chkBtnC) {
element의 style만 수정 -> UI 로직
// order cart show (주문하기 장바구니 출력)
function showCart() {
cartList 배열 데이터를 기준으로 element 생성 후 출력 -> UI 로직
비즈니스로직과 UI로직을 구분할 기준은 도메인 모델 수정 여부이다.
- 헥사고날 아키텍쳐
1-1) show
// 상품 목록에 list 출력
function show() {
// UI 로직
$ul.innerHTML = ''
for (let i = 0; i < list.length; i++) {
let li = document.createElement('li');
li.dataset.id = i;
li.innerHTML = `
<img src="images/img${i}-0.jpg">
<p>${list[i].name}</p>
<span>재고 : ${list[i].stock}</span>
<strong class="ori_p">${priceToString(list[i].price)}원</strong>
<strong class="sal_p">${priceToString(list[i].price * 0.9)}원</strong>
<i class="xi-heart-o"></i>
<i class="xi-cart-add"></i>
`
$ul.append(li)
};
// 콜백 함수
// 비즈니스? UI?
re();
pic();
jjim();
add();
}
- UI 로직
list 객체를 기반으로 요소(li)를 생성(createElement) 한 후 상품 리스트(ul)에 append 하는 함수인데 초기 ul에 공백으로 초기화시켜주는 로직부터 for문까지 데이터의 수정이나 변경이 없어 UI로직으로 구분했다.
- ???
마지막 콜백 함수 자체는 비즈니스인지 UI인지 잘 모르겠지만 해당 함수가 어떤 로직인지에 따라 다르지 않을까..?
1-2) jjim
function jjim() {
$ht.forEach(elemet => {
elemet.addEventListener('click', e => {
// 클릭 한 element의 class에 빈하트/하트인지 구분해서 class를 변경해 준다.
if (e.currentTarget.classList.contains('xi-heart-o')) {
e.currentTarget.classList.remove('xi-heart-o');
e.currentTarget.classList.add('xi-heart');
let id = e.target.parentElement.dataset.id;
// 해당 아이템의 dataset.id를 찜 배열인 idList에 추가
idList.push(id)
} else {
e.currentTarget.classList.remove('xi-heart');
e.currentTarget.classList.add('xi-heart-o');
let id = e.target.parentElement.dataset.id;
idList = idList.filter((ele) => ele !== id)
}
})
});
}
- 비즈니스 로직
찜한 상품의 id가 들어있는 idList 배열에 push/filter후 재할당 하는 것은 비즈니스 로직이라 생각된다.
- UI 로직
단순하게 element의 style, class, id를 수정하는 것은 비즈니스 로직이 아니라고 생각하기에 UI 로직이라고 판단했다.
1-3) cartPM
function cartPM(incBtn, redBtn) {
incBtn.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id
// 상품 id를 기준으로 list.stock를 --
if (list[id].stock !== 0) {
list[id].stock--
list[id].cartStock++
e.currentTarget.nextElementSibling.nextElementSibling.textContent = list[id].cartStock;
// 수량이 증/감된 상품 리스트를 재출력
show();
}
})
redBtn.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id
if (list[id].cartStock !== 1) {
list[id].stock++
list[id].cartStock--
e.currentTarget.nextElementSibling.textContent = list[id].cartStock;
show()
}
})
}
- 비즈니스 로직
장바구니의 아이템의 수량을 증/감 시키면 list의 stock과 cartStock가 수정되므로 비즈니스 로직
- UI 로직
증/감후 show() 함수를 호출해서 수정된 수량을 출력하므로 해당 콜백함수는 UI로직이지 않을까...
그 외에 증가/감소 함수와 제거 함수는 로직이 비슷하므로 생략!
1-4) chkC
function chkC(chkBtnC) {
chkBtnC.addEventListener('click', e => {
e.currentTarget.classList.toggle('on')
})
}
- UI 로직
클릭한 element의 class에 'on'을 추가/제거하는 로직이므로 UI 로직
2. 비즈니스 로직 UI 로직 분리
2-1) jjim
function updateJjimList(id, isAdd) {
if (isAdd) {
idList.push(id);
} else {
idList = idList.filter((ele) => ele !== id);
}
}
function updateHeartButton(element, isFilled) {
if (isFilled) {
element.classList.remove('xi-heart-o');
element.classList.add('xi-heart');
} else {
element.classList.remove('xi-heart');
element.classList.add('xi-heart-o');
}
}
function jjimEventListeners() {
$ht.forEach(element => {
element.addEventListener('click', e => {
let id = e.target.parentElement.dataset.id;
let isAdd = e.currentTarget.classList.contains('xi-heart-o');
updateJjimList(id, isAdd);
updateHeartButton(e.currentTarget, isAdd);
});
});
};
- updateJjimList : idList 데이터를 업데이트해 주는 비즈니스 로직 함수
- updateHeartButton : 찜 버튼의 class를 업데이트하는 UI 로직 함수
- jjimEventListeners : 찜 버튼에 event를 걸어주는 UI 로직 함수
Vanilla.js 특성을 고려하여 상품 목록이 업데이트될 때마다 찜 버튼에 eventListener를 추가해 주어야 찜 기능을 여러 번 사용할 수 있다.
2-2) cart
찜 함수의 경우 찜 기능을 담당하는 함수가 나 홀로 이기 때문에 모듈성을 고려하지 않아도 됐지만 장바구니와 상품 리스트 데이터에 수량이 증가/감소하는 함수들은 한 번 생각해 볼 필요가 있다.
2차 회고 게시글의 예시처럼 계산기 애플리케이션을 만들 경우 숫자 두 개를 매개변수로 받고 각각 기능(+, -, *, /)에 맞는 함수로 전달해 준다면 캡슐화를 고려한 로직이 될 것 같다.
add, cartPM, cartRemove 세 함수 모두 list의 데이터를 업데이트한다는 공통점이 있으므로 updateStock 함수를 만들면 좋다고 생각했다. 그리고 매개변수의 종류와 부가적인 로직을 판단하기 위해 함수들이 어떤 데이터를 수정하는지 먼저 확인했다.
add
-> list.stock, list.cartStock, list.cart,cartList
cartPM
-> list.stock, list.cartStock
cartRemove
-> list.stock, list.cartStock, list.cart,cartList,list.heart
UNION vs INTERSECT
updateStock 함수는 매개변수를 몇 가지 받아야 하는지에 대해 잠시 고민해 보았는데, 함수명 자체도 stock를 업데이트해주는 것이고 부가적인 업데이트들은 각각의 함수에서 하는 것이 재사용성 면에서도 좋아 보였다.
function updateStock(id, stockDelta, cartStockDelta) {
list[id].stock += stockDelta;
list[id].cartStock += cartStockDelta;
}
- updateStock : 업데이트하고자 하는 상품의 id와 변동값을 넘겨주어 수정하는 비즈니스 로직 함수
function addToCart(id) {
if (!cartList.includes(id) && list[id].stock !== 0) {
list[id].cart = true;
updateStock(id, -1, 1);
cartList.push(id);
}
}
function add() {
$add.forEach(ele => {
ele.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id;
addToCart(id);
if (list[id].cart) {
let li = document.createElement('li');
.
.
.
<생략>
.
.
.
li.append(remBtn);
cartPM(incBtn, redBtn);
cartRemove(remBtn);
show();
}
});
});
}
- addToCart : 상품 id를 전달받아 cartList에 존재하지 않고(이미 담긴 상품) 재고가 남아있을 경우 updateStock 함수 호출하는 비즈니스 로직 함수
- add : 상품 리스트에 있는 장바구니 버튼을 클릭했을 때 장바구니에 element가 추가되고 새로고침 해주는 UI 로직 함수
function cartPM(incBtn, redBtn) {
incBtn.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id
if(list[id].stock !== 0){
updateStock(id, -1, 1);
}
e.currentTarget.nextElementSibling.nextElementSibling.textContent = list[id].cartStock;
show();
})
redBtn.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id
if(list[id].cartStock !== 1){
updateStock(id, 1, -1);
}
e.currentTarget.nextElementSibling.textContent = list[id].cartStock;
show()
})
}
- cartPM : add 함수를 통해 생성된 element에 event를 추가해 주는 비즈니스 로직 함수
인데....
장바구니에 담긴 상품을 담으면 li가 추가된다.........
function removeFromCart(id) {
updateStock(id, list[id].cartStock, -list[id].cartStock);
list[id].cart = false;
list[id].heart = false;
cartList = cartList.filter(item => item !== id);
}
function cartRemove(remBtn) {
remBtn.addEventListener('click', e => {
let id = e.currentTarget.parentElement.dataset.id;
removeFromCart(id);
e.currentTarget.parentElement.remove();
show();
});
}
- removeFromCart : 장바구니에 담긴 수량(list.cartStock)을 모두 재고(list.stock)에 더해지고 list.cartStock는 0이 되도록 updateStock에 넘겨주는 비즈니스 로직 함수
- cartRemove : add 함수를 통해 생성된 element에 event를 추가해 주고 새로고침 하는 UI 로직 함수
3. 결과
add ┒
cartPM ┞ updateStock
cartRemove ┛
확실히 공통 작업을 하는 부분을 함수로 만들어 관리하니 보기도 좋고 훨씬 간결해진 것 같다.
그리고 하나의 함수에 최대한 함수명에 충실한 한 가지 기능만 작성하려고 하니 가독성이 증가되어 기분이 좋아졌다.
4. 후기
한가지 고쳐야 할 에러가 있긴 하지만... 그래도 처음치곤 분리를 잘 해낸 것 같다.
오늘 연습해 본 Vanilla.js 특성상 element를 생성할 때마다 event를 적용시켜야 해서 어려운 부분도 있었지만 모듈성을 고려해 같은 기능을 하는 함수들을 묶어 분리해 보니 잘 마무리할 수 있었다.
📝 오늘의 3줄 요약
1. 추상화 - 모듈화를 잘하기 위해 함수/컴포넌트가 어떤 기능을 하는지 먼저 파악하자.
2. 미리 알았더라면 일찍 퇴근할 수 있다.
3. 지금이라도 늦지 않았다.
🐛 버그 수정 (12/12)🐛
function addToCart(id) {
console.log(2);
if (!cartList.includes(id) && list[id].stock !== 0) {
list[id].cart = true;
updateStock(id, -1, 1);
cartList.push(id);
console.log(3);
}
}
function add() {
$add.forEach(ele => {
ele.addEventListener('click', e => {
console.log(1);
let id = e.currentTarget.parentElement.dataset.id;
console.log(list[id].cart);
addToCart(id);
console.log(list[id].cart);
if (list[id].cart) {
console.log(4);
console.log(list[id].cart);
// 1번 상품 장바구니 버튼 클릭
main.js:290 1
main.js:292 false
main.js:278 2
main.js:283 3
main.js:294 true
main.js:296 4
main.js:297 true
// 1번 상품 장바구니 버튼 클릭
main.js:290 1
main.js:292 true
main.js:278 2
main.js:294 true
main.js:296 4
main.js:297 true
console.log로 확인을 해보니 두번째 클릭 때 4가 찍히면 안되는데 if문의 true 조건이 돌아가고 있었다.
forEach속 if 조건문과 addToCart 함수 호출 위치의 변경이 필요해 보인다.
function addToCart(id) {
console.log(2);
if (!cartList.includes(id) && list[id].stock !== 0) {
list[id].cart = true;
updateStock(id, -1, 1);
cartList.push(id);
console.log(3);
}
}
function add() {
$add.forEach(ele => {
ele.addEventListener('click', e => {
console.log(1);
let id = e.currentTarget.parentElement.dataset.id;
console.log(list[id].cart);
console.log(list[id].cart);
if (!list[id].cart) {
console.log(4);
console.log(list[id].cart);
addToCart(id);
// 1번 상품 장바구니 버튼 클릭
main.js:290 1
main.js:292 false
main.js:293 false
main.js:295 4
main.js:296 false
main.js:278 2
main.js:283 3
// 1번 상품 장바구니 버튼 클릭
main.js:290 1
main.js:292 true
main.js:293 true