Dev/아키텍처

[아키텍처] MVC 아키텍처

더움바다 2025. 1. 25. 02:33

https://developer.mozilla.org/ko/docs/Glossary/MVC

 

Model

  • 앱의 데이터와 비즈니스 로직을 관리.
  • 데이터의 상태를 정의하고 이를 변경하는 기능 제공.
// 유저 모델
class UserModel {
  constructor() {
    this.users = [];
  }

  // 데이터 추가
  addUser(user) {
    if (user.name && user.email) {
      this.users.push(user);
    } else {
      throw new Error('유효하지 않은 사용자 데이터');
    }
  }

  // 데이터 수정
  updateUser(id, updatedData) {
    const userIndex = this.users.findIndex((user) => user.id === id);
    if (userIndex !== -1) {
      this.users[userIndex] = { ...this.users[userIndex], ...updatedData };
    } else {
      throw new Error('사용자를 찾을 수 없음');
    }
  }

  // 데이터 삭제
  deleteUser(id) {
    this.users = this.users.filter((user) => user.id !== id);
  }

  // 데이터 조회
  getUser(id) {
    return this.users.find((user) => user.id === id);
  }
  
  // 데이터 조회
  getUsers() {
    return this.users;
  }
}

 

View

  • 사용자에게 데이터를 시각적으로 표시하는 역할.
  • Model의 데이터를 기반으로 UI를 렌더링.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>User Management</title>
</head>
<body>
  <h1>User Management</h1>

  <!-- 사용자 추가 폼 -->
  <form id="addUserForm">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" required>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
    <button type="submit">Add User</button>
  </form>

  <hr>

  <!-- 사용자 목록 -->
  <h2>User List</h2>
  <ul id="userList"></ul>

  <script>
    // Controller와 통신을 담당
    class UserView {
      constructor(apiUrl) {
        this.apiUrl = apiUrl;
        this.userListElement = document.getElementById('userList');
        this.addUserForm = document.getElementById('addUserForm');
        this.init();
      }

      // 초기화
      init() {
        this.loadUsers();
        this.setupEventListeners();
      }

      // 사용자 목록 로드
      async loadUsers() {
        try {
          const response = await fetch(`${this.apiUrl}/users`);
          if (!response.ok) throw new Error('Failed to fetch users');
          const users = await response.json();
          this.renderUserList(users);
        } catch (error) {
          console.error(error.message);
        }
      }

      // 사용자 목록 렌더링
      renderUserList(users) {
        this.userListElement.innerHTML = ''; // 기존 목록 초기화
        users.forEach((user) => {
          const li = document.createElement('li');
          li.textContent = `${user.name} (${user.email})`;
          li.id = `user-${user.id}`;

          // 삭제 버튼 추가
          const deleteButton = document.createElement('button');
          deleteButton.textContent = 'Delete';
          deleteButton.onclick = () => this.deleteUser(user.id);

          li.appendChild(deleteButton);
          this.userListElement.appendChild(li);
        });
      }

      // 사용자 추가
      async addUser(user) {
        try {
          const response = await fetch(`${this.apiUrl}/users`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(user),
          });
          if (!response.ok) throw new Error('Failed to add user');
          const newUser = await response.json();
          this.loadUsers(); // 목록 새로고침
        } catch (error) {
          console.error(error.message);
        }
      }

      // 사용자 삭제
      async deleteUser(userId) {
        try {
          const response = await fetch(`${this.apiUrl}/users/${userId}`, {
            method: 'DELETE',
          });
          if (!response.ok) throw new Error('Failed to delete user');
          this.loadUsers(); // 목록 새로고침
        } catch (error) {
          console.error(error.message);
        }
      }

      // 이벤트 리스너 설정
      setupEventListeners() {
        this.addUserForm.addEventListener('submit', (event) => {
          event.preventDefault();
          const name = this.addUserForm.name.value;
          const email = this.addUserForm.email.value;
          if (name && email) {
            this.addUser({ name, email });
            this.addUserForm.reset(); // 폼 초기화
          }
        });
      }
    }

    // UserView 인스턴스 생성 및 초기화
    const userView = new UserView('http://localhost:3000'); // API URL 설정
  </script>
</body>
</html>

 

Controller

  • 사용자 입력(이벤트)을 받아 ModelView를 조율.
  • Model에서 데이터를 가져오거나 수정하고, View에 전달하여 화면을 업데이트.
// 유저 컨트롤러
class UserController {
  constructor(userModel) {
    this.userModel = userModel;
  }

  // 사용자 추가
  createUser(req, res) {
    try {
      const { id, name, email } = req.body; // 요청에서 사용자 데이터 가져오기
      this.userModel.addUser({ id, name, email });
      res.status(201).json({ message: '사용자 생성 성공', user: { id, name, email } });
    } catch (error) {
      res.status(400).json({ message: error.message });
    }
  }

  // 사용자 조회
  getUser(req, res) {
    try {
      const { id } = req.params; // 요청에서 사용자 ID 가져오기
      const user = this.userModel.getUser(id);
      if (user) {
        res.status(200).json({ user });
      } else {
        res.status(404).json({ message: '사용자를 찾을 수 없음' });
      }
    } catch (error) {
      res.status(500).json({ message: error.message });
    }
  }
  
  // 사용자 리스트 조회
  getUsers(req, res) {
    try {
      const users = this.userModel.getUsers();
      if (users) {
        res.status(200).json({ users });
      } else {
        res.status(404).json({ message: '사용자 리스트를 찾을 수 없음' });
      }
    } catch (error) {
      res.status(500).json({ message: error.message });
    }
  }

  // 사용자 수정
  updateUser(req, res) {
    try {
      const { id } = req.params; // 요청에서 사용자 ID 가져오기
      const updatedData = req.body; // 요청에서 업데이트 데이터 가져오기
      this.userModel.updateUser(id, updatedData);
      res.status(200).json({ message: '사용자 수정 성공' });
    } catch (error) {
      res.status(400).json({ message: error.message });
    }
  }

  // 사용자 삭제
  deleteUser(req, res) {
    try {
      const { id } = req.params; // 요청에서 사용자 ID 가져오기
      this.userModel.deleteUser(id);
      res.status(200).json({ message: '사용자 삭제 성공' });
    } catch (error) {
      res.status(400).json({ message: error.message });
    }
  }
}

 

동작 원리

  1. 사용자가 View를 통해 애플리케이션에 입력을 제공. (위의 User View에서 <form id='addUserForm'> 부분)
  2. Controller가 사용자의 입력을 처리하여 Model에 요청(데이터 변경 또는 조회). (UserView의 addUser -> UserController의 createUser -> UserModel의 addUser -> ...)
  3. Model은 데이터를 업데이트하거나 상태를 반환. (UserView의 getUser -> UserController의 getUser -> UserModel의 getUsers)
  4. ViewModel로부터 데이터를 받아 UI를 갱신하여 사용자에게 표시. (UserView의 renderUserList)

장점

  • 각 구성 요소가 독립적이므로 유지보수와 확장이 용이.
  • ModelView가 분리되어 서로 독립적으로 변경 가능.
    • Model -> Controller -> View를 통해 상호작용하기 때문에 Model의 변경이 View에 직접적인 영향을 주지 않는다.
    • 그렇기에 Model의 변화가 View에 주는 영향을 최소화할수 있고, 또한 View의 변화는 Model에 영향을 주지 않는다.
  • 동일한 Model을 여러 View에서 사용할 수 있음.
    • Model이 독립적으로 구성되어 있기에 재사용이 가능하다.

단점

  • 작은 애플리케이션에서는 MVC를 구현하는 것이 불필요하게 복잡할 수 있음.
    • 작은 규모의 작업에서는 아키텍처를 구현하는 것이 불필요할수도 있다.
  • ViewControllerModel 간의 데이터 전달로 인해 성능에 영향을 줄 수 있음.
    • View  Controller  Model 단계를 거치며 각 계층에서 추가적인 작업을 각각 수행하므로, 복잡성이 늘어날수록 성능에 악영향을 끼칠수 있음.
    • MVC에서 각 계층은 독립적이지만 Controller가 모든 요청을 조율하기 때문에 Controller가 비대해질수 있다. 그로 인해 Controller의 수정이나 확장이 어려워질수 있다.

'Dev > 아키텍처' 카테고리의 다른 글

[아키텍처] MVP 아키텍처  (0) 2025.01.27