BindModel 과 Vue Mix 비교

설명

폴더 구조

vue-mix/
├── componets/
   ├── NoticeForm.js
   └── NoticeList.js
├── app.js
└── admin.html

app.js : Vue


이 코드는 비즈니스 로직과 발표의 핵심을 구현하는 코드입니다.

import NoticeList from './components/NoticeList.js';
import NoticeForm from './components/NoticeForm.js';

const { createApp, ref } = Vue

const app = createApp({
    data() {
        return {
        notices: [],
        selectedNotice: null,
        statusOptions: {
            'D': 'Standby',
            'A': 'Activation',
            'H': 'Hidden'
        }
        };
    },
    methods: {
        fetchNotices() {
            axios.get('/notice/data/list.json')
            .then(response => {
                this.notices = response.data.rows.map(row => ({
                ntc_idx: row.ntc_idx,
                title: row.title,
                contents: row.contents,
                top_yn: row.top_yn,
                active_cd: row.active_cd,
                create_dt: row.create_dt
                })) || [];
            })
            .catch(error => {
                console.error('Error fetching notices:', error);
            });
        },
        selectNotice(notice) {
            this.selectedNotice = notice;
            axios.get(`/notice/data/list.json`)  // RESTful : `/notice/data/${ntc_idx}.json`
            .then(response => {
            const notice = response.data;
            this.selectedNotice = {
                ntc_idx: notice.ntc_idx,
                title: notice.title,
                contents: notice.contents, 
                top_yn: notice.top_yn,
                active_cd: notice.active_cd,
                create_dt: notice.create_dt
            };
            })
            .catch(error => {
            console.error('Error reading notice:', error);
            });
        },
        deselectNotice() {
            this.selectedNotice = null;
        },
        updateNotice(notice) {
            const isValid = this.validateNotice(notice);

            if (isValid) {
                axios.put(`/notice/data/list.json`, notice) // RESTful : '/notice/update/${notice.ntc_idx}`
                .then(() => {
                    console.warn('Caution: Send to the test server, but the data is not reflected.', notice);
                    this.deselectNotice();
                })
                .catch(error => {
                    console.error('Error updating notice:', error);
                });
            } else {
                alert('Validation failed');
            }
        },
        deleteNotice(idx) {
            const deleteNotice = {
                ntc_idx: idx
            };
            if (confirm('Are you sure you want to delete it?')){
                axios.delete(`/notice/data/list.json`, deleteNotice)  // RESTful : `/notice/delete/${ntc_idx}`
                .then(() => {
                    console.warn('Caution: Send to the test server, but the data is not reflected.', deleteNotice);
                    this.deselectNotice();
                    // 삭제 후 공지사항 목록을 다시 불러옴
                    this.fetchNotices();
                })
                .catch(error => {
                    console.error('Error deleting notice:', error);
                });
            }
        },
        validateNotice(notice) {
            let isValid = true;
            if (!notice.title || notice.title === '') {
                isValid = false;
            }
            if (!notice.top_yn || (notice.top_yn !== 'Y' && notice.top_yn !== 'N')) {
                isValid = false;
            }
            if (!notice.active_cd || (notice.active_cd !== 'D' && notice.active_cd !== 'A' && notice.active_cd !== 'H')) {
                isValid = false;
            }
            return isValid;
        }
    },
    mounted() {
        this.fetchNotices();
    },
    components: {
        'notice-list': NoticeList,
        'notice-form': NoticeForm
    }
});

app.mount('#app');

app.js : Vue 와 BindModel Mix


import NoticeList from './components/NoticeList.js';
import NoticeForm from './components/NoticeForm.js';
import NoticeAdminService from './service/notice-admin-svc.js'

const { createApp, ref } = Vue
const bm = new _L.BindModel(new NoticeAdminService());  

bm.url =' /notice/data/list.json';

const app = createApp({
    data() {
        return {
            notices: [],
            selectedNotice: null,
            statusOptions: {
                'D': 'Standby',
                'A': 'Activation',
                'H': 'Hidden'
            },
            bindModel: bm,
        };
    },
    methods: {
        selectNotice(idx) {
            this.selectedNotice = idx;
        },
        deselectNotice() {
            this.selectedNotice = null;
        },
    },
    components: {
        'notice-list': NoticeList,
        'notice-form': NoticeForm
    }
});

app.mount('#app');

notice-admin-svc.js : Vue 와 BindModel Mix


export default class NoticeAdminService {
    constructor() {
        var _this       = this;
        var _template   = null;     // Handlebars template

        this.items = {
            ntc_idx: { required: true },
            title: { required: true }
        },
        this.command = {
            create:     {},
            read:       {
                outputOption: 3,
            },
            update:     {
                cbBind(bind, cmd, setup) {
                    console.warn('Caution: Send to the test server, but the data is not reflected.', setup.data);
                },
            },
            delete:     {
                cbValid(valid, cmd) { 
                    if (confirm('Are you sure you want to delete it?')) return true;
                },
                cbBind(bind, cmd, setup) {
                    console.warn('Caution: Send to the test server, but the data is not reflected.', setup.data);
                },
            },
            list:       {
                outputOption: 1,
            }
        };

        this.mapping = {
            ntc_idx:    { read:     ['bind', 'output'],     update:  'bind',               delete:     ['valid', 'bind'] },
            title:      { read:     'output',               update:  ['valid', 'bind'], },
            contents:   { read:     'output',               update:  'bind' },
            top_yn:     { read:     'output',               update:  ['valid', 'bind'], },
            active_cd:  { read:     'output',               update:  ['valid', 'bind'], },
            create_dt:  { read:     'output' },
        };
    }    
}

비교

  1. 비즈니스 로직의 격리 및 재사용 가능성: 두 번째 코드는 비즈니스 로직을 BindModel과 NoticeAdminService를 사용하여 별도의 클래스로 분리했습니다. 따라서 비즈니스 로직은 Vue 구성 요소와 분리되어 재사용할 수 있습니다. 이 구조를 통해 다른 Vue 구성 요소나 프로젝트에서 동일한 비즈니스 로직을 재활용할 수 있어 유지보수 및 확장성이 크게 향상됩니다.
  2. 데이터 바인딩의 단순성: BindModel을 사용하여 데이터를 가져오는 것과 같은 논리를 캡슐화하면 Vue 구성 요소에 대한 메서드 수를 줄일 수 있습니다. 첫 번째 코드는 직접 축시오스 호출을 사용하지만 두 번째 코드는 BindModel에서 이를 담당하므로 코드가 훨씬 간단해집니다.
  3. 유지보수 및 확장성: 비즈니스 로직은 Vue 구성 요소에 직접 의존하지 않기 때문에 향후 API 변경이나 서비스 로직 변경은 NoticeAdminService에서만 수정할 수 있습니다. 이렇게 하면 코드 유지보수가 간소화됩니다. 반면에 첫 번째 코드는 Vue 구성 요소 내부에 비즈니스 로직이 포함되어 있어 로직을 변경할 때 여러 구성 요소를 수정해야 할 수 있습니다.
  4. 테스트 가능성 향상: 두 번째 방법은 비즈니스 로직을 별도의 클래스로 분리했기 때문에 해당 클래스에 대해 독립적으로 단위 테스트를 작성할 수 있습니다. Vue 구성 요소 내부의 비즈니스 로직은 테스트하기가 더 복잡하지만 BindModel 방법은 테스트 가능한 코드 구조를 제공합니다.

결론적으로 두 번째 코드의 장점은 비즈니스 로직을 분리하여 코드의 재사용 가능성, 유지보수 및 테스트 가능성을 높이는 것입니다.