Single Source Of Truth 백엔드 개발자라면, 자주 들어보았을 것이고, 아쉽게도 프런트엔드만 개발했다면 들어 기회가 별로 없다.
하지만, 대부분의 개발자들이 지키고 있을 원칙이다.
위의 말은 되게 어려우니, 간단하게 알아보도록 하자.
자 위와 같은 데이터가 있다고 하자. 빨간 라인이 일단 잘 못 되었다는 것을 느낄 것이다.
[{a:1, b:2, sum:5}, {a:2, b:3, sum:5}, {a:3, b:4, sum:6}]
이런 문제가 왜 생기는 것일까? SUM은 A, B의 파생 데이터지만, 위의 상황에서는 A, B의 값을 참고해서, 직접 계산한 값을 SUM의 필드로 관리하고 있는 것이 문제다.
=> 즉 A, B의 값이 복사 되어 SUM 필드에서 또 관리가 되고 있는 샘이다.
자 이 형태는 어떤가? SUM은 이제 A와 B의 참조만 이루어 지고, 결과 값으로 자연스럽게 두 개의 필드의 합이 계산될 것이다.
=> A, B의 값이 변경 되는 경우 SUM을 수정할 필요가 없어지기 때문에, SSOT를 잘 지키고 있는 셈이다.
자 실전의 예제를 한번 보도록 하자.
자 실전의 예제를 한번 보도록 하자.
<template>
<div v-for="user in userList" :key="user.userId">
<label>
{{user.name}}
<input type="checkbox" v-model="user.isChecked">
</label>
<button @click="refreshUser(user.userId)">유저 정보 갱신</button>
</div>
</template>
<script>
const getUserList = () => [{userId: 1, name: '홍길동'}, {userId: 2, name: '홍길동2'}]
const getUser = (userId) => getUserList().find(user => user.userId === userId)
export default {
name: 'App',
data() {
return {
userList: []
}
},
mounted() {
this.refreshUserList()
},
methods: {
refreshUserList() {
this.userList = getUserList()
},
refreshUser(userId) {
const userIdx = this.userList.findIndex(user => user.userId === userId)
if(userIdx >= 0) {
this.userList = this.userList.with(userIdx, getUser(userId)).filter(user => user);
console.log(this.userList)
}
},
}
}
</script>
자 getUserList와 getUser의 경우에는 api라고 생각을 하도록 하고, 위의 코드를 보도록 하자,
이 코드가 무엇이 문제일까? 일단 처음으로 지적을 받을 만한 부분은 UserVO에는 isChecked없었는데, 추가가 되었다.
이 코드가 무엇이 문제일까? 일단 처음으로 지적을 받을 만한 부분은 UserVO에는 isChecked없었는데, 추가가 되었다.
api에는 없었지만, 새로 생긴 데이터이기 때문에, 단일 진실 공급원을 위배하게 된 것이라고 보면 된다.
문제를 일으키는 상황이 이렇다. checkbox를 클릭하여, true로 isChecked를 true로 만들어 보자. 그리고 나서, "유저 정보 갱신"버튼을 클릭하게 되면, 우리는 체크한 유저를 잃어 버리게 된다....
문제를 일으키는 상황이 이렇다. checkbox를 클릭하여, true로 isChecked를 true로 만들어 보자. 그리고 나서, "유저 정보 갱신"버튼을 클릭하게 되면, 우리는 체크한 유저를 잃어 버리게 된다....
자 이제 아래의 코드를 다시 한 번 보자.
<template>
<div v-for="user in renderUserList" :key="user.userId">
<label>
{{user.name}}
<input type="checkbox" :value="user.isChecked" @input="onCheckUser(user.userId, !user.isChecked)">
</label>
<button @click="refreshUser(user.userId)">유저 정보 갱신 {{user.isChecked}}</button>
</div>
</template>
<script>
const getUserList = () => [{userId: 1, name: '홍길동'}, {userId: 2, name: '홍길동2'}]
const getUser = (userId) => getUserList().find(user => user.userId === userId)
export default {
name: 'App',
data() {
return {
userList: [],
// 사용자를 체크했는지 여부를 데이터를 저장한다.
// 프런트엔드에서 추가된 데이터이다.
checkedUserList: []
}
},
computed: {
// userList와 checkedUserList를 조합하여,
// 노출할 데이터를 따로 보관한다.
renderUserList() {
return this.userList.map(user => ({
...user,
isChecked: this.checkedUserList.includes(user.userId)
}))
}
},
mounted() {
this.refreshUserList()
},
methods: {
refreshUserList() {
this.userList = getUserList()
},
refreshUser(userId) {
const userIdx = this.userList.findIndex(user => user.userId === userId)
if(userIdx >= 0) {
this.userList = this.userList.with(userIdx, getUser(userId)).filter(user => user);
console.log(this.userList)
}
},
onCheckUser(userId, isChecked) {
if(isChecked) {
this.checkedUserList.push(userId)
} else {
this.checkedUserList = this.checkedUserList.filter(checkedUserId => checkedUserId !== userId)
}
},
}
}
</script>
코드는 생각보다 늘어나게 되었다. 하지만, 위처럼 데이터를 따로 보관하고 있기 때문에, 프런트엔드에서 보관한 데이터를 유실하지 않게 되었고,
만약, userList에 isChecked를 추가한 상태로, 위와 같이 사용자의 정보를 갱신하는 경우에 유지 시켜달라는 요청이 있었다면.... 위처럼 코드를 작성하는 것이 얼마나 편안한지 다시 알아보게 될 것이다.
문제는 지금은 isChecked 하나만 변수가 추가 되었지만, 실 업무에서는 그러지 않을 것이다.
댓글
댓글 쓰기