feat!: 室員集約の再設計#94
Merged
KinjiKawaguchi merged 22 commits intodevelopfrom Mar 23, 2026
Merged
Conversation
室員管理の仕様に基づきMember集約をリモデリング: - 識別子をUUIDから大学メールアドレスに変更 - discriminated unionで室員/未確認/元室員の3状態を型レベルで表現 - 状態に応じた属性の有無を型システムで強制 - イミュータブルな集約(操作は新インスタンスを返す) - ドメインイベントの定義と集約内での収集機構を追加 - DiscordAccountを集約から除外(外部サービス連携コンテキストへ移行予定) - Departmentを削除しAffiliation(shared kernel)で所属を表現 BREAKING CHANGE: Member集約のAPIが全面的に変更 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ExternalServiceLink集約: 外部サービスとのアカウント紐付けを独立した境界づけられたコンテキストとして新設 - Faculty集約: 学部・学科のマスターデータ管理(学科の追加・削除、同名重複チェック) - GraduateSchool集約: 研究科・専攻のマスターデータ管理(課程別管理、専攻の追加・削除) - 不要な例外を削除(DiscordAccount系、InvalidDepartment)し、マスターデータ用例外を追加 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
組織構造はuniversityStructure.tsの型定義で管理されており、 ランタイムCRUD用のエンティティは不要。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MemberSuspendedをMemberUnconfirmedにリネーム(クラス名・メソッド名との整合性) - 未使用のExternalServiceUnlinkedイベントを削除(YAGNI) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
対応サービスを型レベルで制限し、不正な値の混入を防ぐ。 現時点ではDiscordのみ対応。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UUIDをDBのPKおよびEvent集約からの参照用の技術的識別子として保持する。 emailはビジネス上の識別子として併存する。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
汎用的なExternalServiceLinkはSpeculative Generalityだったため削除し、 Discord固有のDiscordAccount集約として再設計: - DiscordId(Branded Type)で型安全に識別 - nickNameをDiscord固有の属性として保持 - MemberIdでMember集約を参照 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Member集約リモデリングのExpand-Contract移行のPhase 1。 既存のdepartment列は維持したまま、新列を非破壊的に追加する。 - status: member_status enum(active/unconfirmed/former)、デフォルト'active' - affiliation: 所属情報(JSONB、SerializedAffiliation型で型安全に保存) - DrizzleMemberRepositoryを新Member型(ActiveMember/UnconfirmedMember/FormerMember)に対応 - SerializedAffiliation型をKarteリポジトリと同じパターンで定義 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CS/BI/IA → 情報学部の各学科affiliationに自動マッピング。 ALUMNI → status='former'に変更。 GRADUATE/OTHERSは手動対応としてスキップ。 yearはメールアドレスの入学年から自動計算。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expand-Contract移行のPhase 3: - department列を削除(Affiliationに置き換え済み) - student_idをvarchar(8) nullableに変更(未確認/元室員は持たない) - timestamp precisionを削除(ミリ秒精度は不要) - リポジトリのTODOコメントを解消 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
personalEmailは任意項目のため、null/undefinedではなくRecorded<Email>で 記録有無を明示する。Recorded型をKarte集約からshared kernelに移動。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Recorded型をshared kernelに追加 - DrizzleMemberRepositoryのpersonalEmailをRecorded<Email>でマッピング - membersスキーマ: department列削除、student_id varchar(8) nullable化、timestamp precision削除 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25b54f9 to
1dfdbcf
Compare
49b7baf to
4de8b3c
Compare
既存ユースケースのI/Oを新Member型に合わせて書き直し: - RegisterMember: Department→Affiliationベース、イミュータブル集約のregister()使用 - UpdateMember: イミュータブル集約のchangeName/changeStudentId/changePersonalEmail使用 - Discord系: DiscordAccountRepositoryを使う形に書き直し - GetMember/GetMemberByEmail/GetMemberList: 型互換のため変更なし Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4de8b3c to
d969a25
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
member_status enum追加、status列・affiliation列追加、department列削除、 student_idをvarchar(8) nullableに変更、timestamp precision削除。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- InvalidAffiliationOperationExceptionに操作名・現在の所属種別・理由を持たせ、 エラー原因を特定しやすくした - 汎用ErrorをApplicationExceptionに置き換え: - DiscordAccountNotFoundException(Discord検索失敗時) - MemberNotActiveException(室員以外への操作時) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
findById: string → MemberId findByEmail: string → UniversityEmail Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
drizzle-kit生成ファイルのフォーマットはdrizzle管轄のため。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Member集約を「室員/未確認/元室員」の3状態モデルへ再設計し、DiscordAccountを独立集約として分離することで、状態遷移と所属(Affiliation)を型レベルで安全に扱えるようにするPRです(ドメイン〜アプリケーション〜インフラまでBREAKING)。
Changes:
- Member集約を
ActiveMember | UnconfirmedMember | FormerMemberのUnionに刷新し、ドメインイベント収集(MemberEvent)を追加 - DiscordAccountを独立集約化し、UseCase/Repositoryを分離して注入経路を更新
- DBスキーマを
member_statusenum +affiliation(JSONB) へ移行し、Drizzle永続化を対応
Reviewed changes
Copilot reviewed 34 out of 34 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/domain/aggregates/member/Member.test.ts | 3状態の振る舞い・遷移・イベント蓄積のテスト追加 |
| src/infrastructure/drizzle/schema.ts | membersにstatus/affiliationを追加しdepartmentを削除 |
| src/infrastructure/drizzle/index.ts | DrizzleDiscordAccountRepositoryを公開 |
| src/infrastructure/drizzle/DrizzleMemberRepository.ts | 3状態Memberの復元/保存とAffiliationの(逆)シリアライズ |
| src/infrastructure/drizzle/DrizzleDiscordAccountRepository.ts | DiscordAccountの永続化リポジトリ新規追加 |
| src/executable/member.ts | Member/Discord reposの生成とUseCaseへのDI更新 |
| src/domain/shared/index.ts | sharedからRecordedを再export |
| src/domain/shared/Recorded.ts | null回避のためのRecorded型を追加 |
| src/domain/exceptions/DomainExceptions.ts | InvalidAffiliationOperationException追加、旧Discord/Department例外削除 |
| src/domain/base/index.ts | DomainEventをexport対象に追加 |
| src/domain/base/DomainEvent.ts | DomainEvent基底インターフェース追加 |
| src/domain/aggregates/member/index.ts | MemberEventをexport、旧DiscordAccount/Departments export削除 |
| src/domain/aggregates/member/MemberRepository.ts | findByEmailをUniversityEmail受けに変更、Discord検索/delete削除 |
| src/domain/aggregates/member/MemberEvent.ts | Member集約のドメインイベント群を新規追加 |
| src/domain/aggregates/member/Member.ts | Memberを3クラス+Unionに再構成し状態遷移/変更操作を実装 |
| src/domain/aggregates/member/DiscordAccount.ts | Member内DiscordAccountエンティティを削除 |
| src/domain/aggregates/member/Departments.ts | Department ValueObjectを削除 |
| src/domain/aggregates/index.ts | discord-account export追加(集約公開範囲更新) |
| src/domain/aggregates/discord-account/index.ts | discord-account集約のexport追加 |
| src/domain/aggregates/discord-account/DiscordId.ts | DiscordIdのBranded Type追加 |
| src/domain/aggregates/discord-account/DiscordAccountRepository.ts | DiscordAccountRepository追加 |
| src/domain/aggregates/discord-account/DiscordAccount.ts | DiscordAccount集約実装(link/reconstruct等)追加 |
| src/application/usecase/member/index.ts | memberユースケースexport順更新 |
| src/application/usecase/member/UpdateMember.ts | Active限定更新・Immutable更新へ変更 |
| src/application/usecase/member/RegisterMember.ts | ActiveMember.registerを用いるI/Oへ変更 |
| src/application/usecase/member/GetMemberByEmail.ts | 入力をUniversityEmailへ変更 |
| src/application/usecase/member/GetMemberByDiscordId.ts | DiscordRepo→MemberRepoの2段検索へ変更 |
| src/application/usecase/member/ConnectDiscordAccount.ts | DiscordAccount集約を保存する形へ変更 |
| src/application/usecase/member/ChangeDiscordNickName.ts | DiscordAccountRepositoryベースへ変更 |
| src/application/exceptions/ApplicationExceptions.ts | DiscordAccountNotFound/MemberNotActive追加 |
| drizzle/meta/_journal.json | Drizzleジャーナル更新 |
| drizzle/meta/0001_snapshot.json | Drizzle snapshot追加 |
| drizzle/0001_skinny_cannonball.sql | membersのstatus/affiliation等マイグレーション追加 |
| biome.json | drizzleディレクトリをformatter対象外に追加 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- UpdateMember: 型アサーションをナローイングで置き換え - pullDomainEvents → getDomainEventsにリネーム(イミュータブル集約ではクリアしない) - DrizzleMemberRepository: personalEmailのnullチェックを明示的に、deserializeAffiliationにexhaustive check追加 - ConnectDiscordAccount: 既存紐付けの上書き防止チェックを追加 - DrizzleDiscordAccountRepository: save()のonConflictからmemberId更新を除外 - DiscordAccountAlreadyLinkedException を新規追加 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
KinjiKawaguchi
added a commit
that referenced
this pull request
Mar 23, 2026
## Why 室員管理システムのドメインモデル設計書に基づき、Member集約を全レイヤーにわたって再設計する。 現行のMemberは単一クラスで全状態を扱い、Discord連携やDepartment(CS/BI/IA等)を集約内に持つ構造だが、仕様で求められる室員/未確認/元室員の状態遷移やAffiliationベースの所属管理に対応できていない。 BREAKING CHANGE: Member集約のAPI、DBスキーマ、ユースケースI/Oが全面的に変更される。 ## What ### ドメイン層 #### Member集約の3状態モデル - `ActiveMember`(室員): MemberId, email, 名前, 個人メール, 学籍番号, 所属 - `UnconfirmedMember`(未確認): MemberId, email, 名前, 個人メール - `FormerMember`(元室員): MemberId, email, 名前, 個人メール - `type Member = ActiveMember | UnconfirmedMember | FormerMember` #### 設計上の特徴 - **イミュータブル集約**: 全操作が新インスタンスを返す - **型レベルでの状態保証**: 元室員に学籍番号でアクセスしようとするとコンパイルエラー - **二重識別子**: `MemberId`(UUID, Branded Type)は技術的識別子、`UniversityEmail`はビジネス識別子 - **Affiliationによる区分表現**: 学部生/修士/博士の区分はAffiliationの型が内包 - **Recorded\<T\>**: personalEmailの記録有無をnullではなく型で明示(shared kernelに配置) - **ドメインイベント収集**: 12種のイベントを集約内で蓄積し`pullDomainEvents()`で取り出し #### DiscordAccount集約(独立した境界づけられたコンテキスト) - 旧MemberからDiscordAccountを分離し、独自の集約ルートとして再設計 - `DiscordId`(Branded Type)で型安全に識別 - `nickName`をDiscord固有の属性として保持 - `MemberId`でMember集約を参照 #### 削除・整理 - 旧`DiscordAccount`(Member集約内のエンティティ)→ 独立集約に移行 - `Departments`(CS/BI/IA等のenum)→ Affiliation(shared kernel)に置き換え - Discord関連例外、InvalidDepartmentException を削除 ### インフラ層 #### DBスキーマ変更 - `member_status` enum追加(active/unconfirmed/former) - `status`列追加、`affiliation`列追加(JSONB, SerializedAffiliation型) - `department`列削除 - `student_id`: text NOT NULL → varchar(8) nullable - `timestamp`: precision(3)削除 #### リポジトリ - `DrizzleMemberRepository`: 新Member型(ActiveMember/UnconfirmedMember/FormerMember)に対応。SerializedAffiliationでAffiliationを型安全にシリアライズ。 - `DrizzleDiscordAccountRepository`: 新規実装 ### アプリケーション層 #### ユースケースI/O更新 - 全InputをBranded Type/ドメイン型に統一(MemberId, DiscordId, UniversityEmail, StudentId, Recorded\<Email\>等) - RegisterMember: Department→Affiliationベース - UpdateMember: イミュータブル集約のchangeName/changeStudentId/changePersonalEmail使用 - Discord系: DiscordAccountRepositoryを使う形に書き直し #### 例外設計改善 - `InvalidAffiliationOperationException`: operation, currentAffiliationType, reasonフィールドで原因を明示 - `DiscordAccountNotFoundException`, `MemberNotActiveException`を新規追加 - 汎用Errorの使用を排除 ## Test plan - [x] ActiveMember: 登録、復元、除籍、未確認移行、名前変更、学籍番号変更 - [x] ActiveMember: 内部進学(学部→修士、修士→博士、不正遷移のエラー) - [x] ActiveMember: 転学部、転学科(同一学部制約)、転専攻(同一研究科制約) - [x] UnconfirmedMember: 確認復帰、除籍、名前変更 - [x] FormerMember: 再登録、名前変更 - [x] ライフサイクル全体のイベント蓄積(登録→除籍→再登録、登録→未確認→確認) - [x] ドメイン層の型エラー: 0件 - [x] 全64テスト合格 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/su-its/core/pull/94" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
室員管理システムのドメインモデル設計書に基づき、Member集約を全レイヤーにわたって再設計する。
現行のMemberは単一クラスで全状態を扱い、Discord連携やDepartment(CS/BI/IA等)を集約内に持つ構造だが、仕様で求められる室員/未確認/元室員の状態遷移やAffiliationベースの所属管理に対応できていない。
BREAKING CHANGE: Member集約のAPI、DBスキーマ、ユースケースI/Oが全面的に変更される。
What
ドメイン層
Member集約の3状態モデル
ActiveMember(室員): MemberId, email, 名前, 個人メール, 学籍番号, 所属UnconfirmedMember(未確認): MemberId, email, 名前, 個人メールFormerMember(元室員): MemberId, email, 名前, 個人メールtype Member = ActiveMember | UnconfirmedMember | FormerMember設計上の特徴
MemberId(UUID, Branded Type)は技術的識別子、UniversityEmailはビジネス識別子pullDomainEvents()で取り出しDiscordAccount集約(独立した境界づけられたコンテキスト)
DiscordId(Branded Type)で型安全に識別nickNameをDiscord固有の属性として保持MemberIdでMember集約を参照削除・整理
DiscordAccount(Member集約内のエンティティ)→ 独立集約に移行Departments(CS/BI/IA等のenum)→ Affiliation(shared kernel)に置き換えインフラ層
DBスキーマ変更
member_statusenum追加(active/unconfirmed/former)status列追加、affiliation列追加(JSONB, SerializedAffiliation型)department列削除student_id: text NOT NULL → varchar(8) nullabletimestamp: precision(3)削除リポジトリ
DrizzleMemberRepository: 新Member型(ActiveMember/UnconfirmedMember/FormerMember)に対応。SerializedAffiliationでAffiliationを型安全にシリアライズ。DrizzleDiscordAccountRepository: 新規実装アプリケーション層
ユースケースI/O更新
例外設計改善
InvalidAffiliationOperationException: operation, currentAffiliationType, reasonフィールドで原因を明示DiscordAccountNotFoundException,MemberNotActiveExceptionを新規追加Test plan
🤖 Generated with Claude Code