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日目は、
「業務アプリをこういう“役割の塊”として見られるようになる」
ここまで到達できていれば十分です。
次のステップでは、
この設計を実際の画面(ログイン画面・一覧・詳細・編集)に
どうつないでいくかを、少しずつコードで形にしていきます。


