JavaScript | 1 日 120 分 × 7 日アプリ学習:ミニ業務アプリ(総合)

Web APP JavaScript
スポンサーリンク

1日目のゴールと作るアプリのイメージ

この7日間は、
「ちょっと業務っぽいミニアプリ」を題材にして、
設計力・保守性・実務思考を鍛えていきます。

1日目のテーマは、
「いきなりコードを書かず、“設計の目”でアプリを分解すること」です。

題材はこんな感じのミニ業務アプリです。

ログイン画面
ログイン後に「一覧 → 詳細 → 編集」の画面遷移
ユーザーの権限によって「できること/できないこと」が変わる(擬似的でOK)

今日は、これを
「どうクラスに分けるか」
「どこに責任を置くか」
を、コード少なめ・図解多めの感覚でやっていきます。


業務アプリを“画面”ではなく“役割”で分解する

画面ベースで考えると、すぐに破綻する

初心者がやりがちな分解は、こうです。

LoginPage.js
ListPage.js
DetailPage.js
EditPage.js

もちろんこれでも「動くもの」は作れますが、
業務アプリではすぐに限界が来ます。

ログイン状態の管理
ログインユーザーの情報
権限(admin / 一般ユーザー など)
API 通信(サーバーとやりとりする想定)
バリデーション(入力チェック)

これらを全部「画面の中」に書き始めると、
1ファイルがどんどん巨大になり、
「どこに何が書いてあるのか分からない」状態になります。

ここで必要になるのが、
“画面”ではなく“役割”で分ける視点です。

役割ベースでのざっくり分解

このミニ業務アプリを、役割で分けるとこうなります。

ユーザーと権限を扱う役割
ログイン状態を管理する役割
データ(一覧・詳細)を扱う役割
画面の表示と遷移を扱う役割

1日目では、
この「役割」をクラスに落としていくところまでをやります。


class 設計の出発点:User と Auth を考える

User クラスで「ログインユーザー」を表現する

まずは、業務アプリの中心にいる
「ユーザー」という存在をクラスにします。

class User {
  #id;
  #name;
  #role; // "admin" | "staff" | "guest" など

  constructor(id, name, role) {
    this.#id = id;
    this.#name = name;
    this.#role = role;
  }

  getId() {
    return this.#id;
  }

  getName() {
    return this.#name;
  }

  getRole() {
    return this.#role;
  }

  isAdmin() {
    return this.#role === "admin";
  }

  isStaff() {
    return this.#role === "staff";
  }
}
JavaScript

ここでのポイントは、
「ユーザーの権限(role)を、ただの文字列としてバラバラに扱わない」
ということです。

画面のあちこちで

if (user.role === "admin") { ... }
JavaScript

と書き始めると、
スペルミスや条件漏れでバグの温床になります。

User クラスに
isAdmin()isStaff() を用意しておけば、
「権限に関するロジック」は User の中に閉じ込められます。

これが、
カプセル化と責務分離の第一歩です。

Auth クラスで「ログイン状態」をまとめる

次に、「今ログインしているかどうか」を扱うクラスを考えます。

class Auth {
  #currentUser;

  constructor() {
    this.#currentUser = null;
  }

  login(userId, password) {
    if (userId === "admin" && password === "pass") {
      this.#currentUser = new User(1, "管理者", "admin");
      return true;
    }
    if (userId === "staff" && password === "pass") {
      this.#currentUser = new User(2, "一般社員", "staff");
      return true;
    }
    return false;
  }

  logout() {
    this.#currentUser = null;
  }

  isLoggedIn() {
    return this.#currentUser !== null;
  }

  getCurrentUser() {
    return this.#currentUser;
  }
}
JavaScript

ここでは、
実際のサーバー認証はまだやらず、
「擬似ログイン」として
ユーザーIDとパスワードを固定で判定しています。

重要なのは、

ログインしているかどうか
誰がログインしているか

という情報を、
Auth クラスの中に閉じ込めたことです。

画面側は

「ログインしているか?」→ auth.isLoggedIn()
「ログインユーザーは誰か?」→ auth.getCurrentUser()

だけを知っていればよく、
ログインの判定ロジックには踏み込む必要がありません。


一覧・詳細・編集を“データの世界”として設計する

Record クラスで「1件のデータ」を表す

業務アプリでは、
「一覧 → 詳細 → 編集」の対象になる
“1件のレコード”が必ず存在します。

例えば「顧客情報」だとしましょう。

class Customer {
  #id;
  #name;
  #email;

  constructor(id, name, email) {
    this.#id = id;
    this.#name = name;
    this.#email = email;
  }

  getId() {
    return this.#id;
  }

  getName() {
    return this.#name;
  }

  setName(name) {
    this.#name = name;
  }

  getEmail() {
    return this.#email;
  }

  setEmail(email) {
    this.#email = email;
  }
}
JavaScript

ここではまだバリデーションはシンプルにしておきますが、
「1件のデータをクラスで表現する」という形を押さえておきます。

CustomerRepository で「一覧・詳細・保存」をまとめる

次に、「データの集まり」と「操作」をまとめるクラスを作ります。

class CustomerRepository {
  #customers;

  constructor() {
    this.#customers = [
      new Customer(1, "山田太郎", "taro@example.com"),
      new Customer(2, "鈴木花子", "hanako@example.com")
    ];
  }

  findAll() {
    return this.#customers;
  }

  findById(id) {
    return this.#customers.find(c => c.getId() === id) || null;
  }

  save(customer) {
    const existing = this.findById(customer.getId());
    if (!existing) {
      this.#customers.push(customer);
      return;
    }
    existing.setName(customer.getName());
    existing.setEmail(customer.getEmail());
  }
}
JavaScript

ここでの責務分離はこうです。

Customer
1件の顧客の状態と、その変更を担当する。

CustomerRepository
顧客の集まりを管理し、
一覧取得・1件取得・保存を担当する。

画面側は、
「一覧が欲しい」→ repo.findAll()
「このIDの詳細が欲しい」→ repo.findById(id)
「編集結果を保存したい」→ repo.save(customer)

だけを知っていればよく、
内部で配列を使っているのか、
将来サーバーAPIになるのかは気にしなくてよくなります。


権限制御(擬似)を“あちこちに書かない”設計

「この操作をしていいか?」を1か所に集める

業務アプリで一気にややこしくなるのが「権限」です。

管理者だけ編集できる
一般ユーザーは閲覧だけ
ゲストはログイン画面しか見られない

これを画面のあちこちに

if (user.role === "admin") { ... }
JavaScript

と書き始めると、
条件漏れ・コピペミス・仕様変更時の修正漏れが必ず起きます。

そこで、「権限チェック専用のクラス」を用意します。

class Permission {
  static canViewList(user) {
    return !!user; // ログインしていれば誰でもOK
  }

  static canViewDetail(user) {
    return !!user; // ログインしていれば誰でもOK
  }

  static canEdit(user) {
    if (!user) return false;
    return user.isAdmin(); // 管理者だけ編集可
  }
}
JavaScript

ここでの重要ポイントは、

権限に関するルールを
Permission クラスに“集約”したことです。

画面側は、

一覧を表示していいか? → Permission.canViewList(currentUser)
編集ボタンを出していいか? → Permission.canEdit(currentUser)

と聞くだけでよく、
「admin かどうか」という判定ロジックには踏み込みません。

これが、
実務でめちゃくちゃ効く「責務分離」です。


1日目の小さな“つなぎ”の例

ログイン → 一覧表示までの流れをコードでイメージする

まだ本格的な画面は作りませんが、
コンソールベースで「流れ」をイメージしてみます。

const auth = new Auth();
const repo = new CustomerRepository();

function simulateApp() {
  console.log("=== ログイン試行 ===");
  const ok = auth.login("admin", "pass");
  if (!ok) {
    console.log("ログイン失敗");
    return;
  }

  const user = auth.getCurrentUser();
  console.log("ログインユーザー:", user.getName(), user.getRole());

  if (!Permission.canViewList(user)) {
    console.log("一覧を見る権限がありません");
    return;
  }

  console.log("=== 顧客一覧 ===");
  repo.findAll().forEach(c => {
    console.log(c.getId(), c.getName(), c.getEmail());
  });

  if (Permission.canEdit(user)) {
    console.log("このユーザーは編集できます");
  } else {
    console.log("このユーザーは閲覧のみです");
  }
}

simulateApp();
JavaScript

ここで注目してほしいのは、

ログインのロジックは Auth に閉じ込められている
ユーザーの権限ロジックは User と Permission に閉じ込められている
データの取得ロジックは CustomerRepository に閉じ込められている

ということです。

main 的なコード(simulateApp)は、
「誰に何を頼むか」だけを書いていて、
中身の実装には踏み込んでいません。

これが、
設計力・保守性・実務思考の“土台”になる構造です。


1日目でいちばん深掘りしてほしいポイント

今日はあえて、
「画面を作る前の話」に集中しました。

理由はシンプルで、
業務アプリは“画面”ではなく“設計”で決まるからです。

まとめると、こうです。

class
「画面ごと」ではなく「役割ごと」に切る。
User / Auth / Customer / CustomerRepository / Permission のように、
“何を担当するクラスか”を名前で言えるようにする。

カプセル化
User の role、Customer の id/name/email、
Auth の currentUser、Repository の配列など、
中身はすべて private にして、
外からはメソッド経由でしか触れないようにする。
これにより、内部実装を自由に変えられる。

責務分離
ログイン状態 → Auth
ユーザーの権限 → User + Permission
データの集まり → Repository
画面の制御 → これから作る Controller / UI 側

1日目は、
「業務アプリをこういう“役割の塊”として見られるようになる」
ここまで到達できていれば十分です。

次のステップでは、
この設計を実際の画面(ログイン画面・一覧・詳細・編集)に
どうつないでいくかを、少しずつコードで形にしていきます。

タイトルとURLをコピーしました