中央モジュール: イベントと参加者の管理
鼓動する心臓 イベントをプレイする イベントおよび参加者管理モジュールです。プラットフォームのあらゆる機能 イベントの企画から経費の分割まで、この核を中心に展開します。 旅行計画からデータ分析まで、すべてはイベントとその人々から始まります 参加者。
この記事では、ドメインがライフサイクルをどのように形作るかについて詳しく説明します。 を通じたイベントの ステートマシン、参加者が来るにつれて で管理 差別化された役割 そして 完全なRSVPシステム、 そして、REST API がこのすべての機能を一貫して安全に公開する方法について説明します。
この記事でわかること
- ステート マシンによるイベント ライフサイクル: DRAFT から COMPLETED まで
- 参加者の役割: ORGANIZER、CO_ORGANIZER、ATENDEE、VIP
- WAITING、ACCEPTED、DECLINED、MAYBE、CANCELED、および EXPIRED のステータスを持つ RSVP システム
- パブリックトークンとQRコードを使用したチェックインおよびチェックアウトのメカニズム
- イベントの可視性とリンクを介した共有
- キャパシティ管理と参加者のリアルタイムカウント
- イベントと参加者向けの主要な REST API
ドメイン駆動設計におけるイベント集約
実体 イベント それは集約ルート メイン システムの。 DDD の原則に従って、イベントの状態と の参加者がこのルートを通過し、一貫性を確保します。 ビジネスルール。
集計では、タイトル、説明、開始日、およびデータ構造が豊富にカプセル化されています。 目的、場所と地理座標、予算と通貨、支出の内訳の種類、 最大収容人数と参加者の総数。各フィールドが検証される ドメイン内で定義された明示的なビジネス ルールを通じて。
AGGREGATE ROOT: Evento
┌────────────────────────────────────────┐
│ Campi Principali │
│ - id (Long, auto-generated) │
│ - titolo (max 200 caratteri) │
│ - descrizione (TEXT, max 2000) │
│ - stato (StatoEvento enum) │
│ - organizzatoreId (FK utente) │
│ - dataInizio / dataFine (Instant) │
│ - budget (Money Value Object) │
│ - luogo + coordinate GPS │
│ - maxPartecipanti (opzionale) │
├────────────────────────────────────────┤
│ Token di Condivisione │
│ - tokenCondivisione (8 chars) │
│ - tokenCheckin (8 chars) │
│ - tokenCheckout (8 chars) │
├────────────────────────────────────────┤
│ Relazioni │
│ - partecipanti: Set<Partecipante> │
│ (1:N, lazy loading) │
└────────────────────────────────────────┘
パターン ファクトリーメソッド 作成に使用される: コンストラクター
それはプライベートであり静的メソッドです Evento.crea() 検証を扱う
そして初期化。イベントは常に状態で発生します 下書き、
一貫した出発点を確保します。
イベントのライフサイクル: ステート マシン
のあらゆるイベント イベントをプレイする 有限状態マシンとしてモデル化された、明確に定義されたライフサイクルを通過します。 各移行は、ステップを防止するビジネス ルールによって保護されます。 無効です。
┌───────────┐
│ DRAFT │ (Evento creato, in bozza)
└─────┬─────┘
│
pubblica()
│
▼
┌─────────────┐
│ PUBLISHED │ (Visibile, inviti aperti)
└──────┬──────┘
│
conferma()
│
▼
┌─────────────┐
│ CONFIRMED │ (Evento confermato)
└──────┬──────┘
│
avvia()
│
▼
┌───────────────┐
│ IN_PROGRESS │ (Evento in corso)
└───────┬───────┘
│
iniziaDivisioneSpese()
│
▼
┌───────────────────┐
│ EXPENSE_SPLITTING │ (Divisione spese attiva)
└─────────┬─────────┘
│
completa()
│
▼
┌─────────────┐
│ COMPLETED │ (Evento concluso)
└─────────────┘
NOTA: Da qualsiasi stato (tranne COMPLETED e CANCELLED)
si può passare a CANCELLED tramite annulla(motivo)
州の詳細
各状態には正確な意味があり、どの操作が許可されるかを決定します。
- 下書き: イベントは作成されましたが、参加者にはまだ表示されていません。日付はオプションで、タイトルと説明は自由に変更できます。これはあらゆるイベントの初期状態です。
- 公開日: イベントは公開されており、表示されます。公開するには、タイトルと開始日が必要です。招待状を送信し、参加者が応答することができます。
- 確認済み: イベントは確認され、開催されます。主催者がイベントの開催を確認した時点で、PUBLISHED から遷移します。
- 進行中: イベント開催中です。チェックインが可能で、アクティビティはライブです。
- EXPENSE_SPLITTING: イベントは終了し、費用は参加者間で折半されます。このフェーズは 必須 完成前。
- 完了: 経費の分割、残高の計算など、すべてが完了しました。イベントはアーカイブされています。
- キャンセル: イベントは中止となりました。キャンセルには理由が必要で、すべての参加者に通知が生成されます。
移行ルール
状態遷移は列挙型のメソッドによって保護されます StatoEvento それ
いつでも何が許可されるかを決定します。
canAcceptParticipants()
DRAFT, PUBLISHED, INVITATIONS_SENT, CONFIRMED → true
isModifiable()
DRAFT, PUBLISHED, INVITATIONS_SENT → true
isActive()
Tutti tranne CANCELLED e COMPLETED → true
canSplitExpenses()
IN_PROGRESS, EXPENSE_SPLITTING → true
canComplete()
Solo EXPENSE_SPLITTING → true
このアプローチにより、違法な変更の試みが確実に阻止されます。
ドメインレベルで例外をスローする IllegalStateException と
データベースに到達する前に、説明的なメッセージを送信します。
参加者の役割
イベントの各参加者 イベントをプレイする 内部での権限と機能を決定する特定の役割があります。 イベントの様子。システムは 4 つの異なる役割を定義します。
ORGANIZER ("Organizzatore")
└─ Ha creato l'evento
└─ Permessi completi di modifica
└─ Può aggiungere co-organizzatori
└─ Può promuovere/retrocedere partecipanti
└─ Non può essere rimosso dall'evento
└─ Plus-one consentito automaticamente
CO_ORGANIZER ("Co-Organizzatore")
└─ Può modificare l'evento
└─ Può invitare nuovi partecipanti
└─ NON può eliminare l'evento
└─ Plus-one consentito automaticamente
└─ Può essere retrocesso solo dal creatore
ATTENDEE ("Partecipante")
└─ Partecipante standard
└─ Accesso in sola lettura all'evento
└─ Può rispondere all'invito (RSVP)
└─ Può aggiungere restrizioni alimentari
└─ Può invitare altri partecipanti
VIP ("VIP")
└─ Partecipante con status speciale
└─ Stessi permessi di ATTENDEE
└─ Visibilità prioritaria nelle liste
昇進と降格
集合体 Evento プロモーションを管理するための専用メソッドを公開します。
方法 promuoviACoorganizzatore() 参加者が次のことを行っていないことを確認します。
プロモーションを実行する前にすでに主催者または共同主催者である。
降格経由 retroceidiCoorganizzatore() 共催者が報告
出席者の役割に。
重要なビジネス ルール: 参加者が役割に昇格するとき 組織的には、システムは自動的に プラスワン、 主催者がイベントに同伴者を連れてくることを許可します。
RSVP システム
RSVP (Répondez s'il vous plaît) システムは、すべてのフローを管理します。 招待状への返事。各出席者には時間の経過とともに変化する出欠確認ステータスがあります 彼の行動に基づいて。
┌───────────────┐ accettaInvito() ┌─────────────┐
│ IN_ATTESA │ ───────────────────▶ │ ACCETTATO │
└───────┬───────┘ └─────────────┘
│
├── rifiutaInvito() ──▶ RIFIUTATO
│
├── rispondiForse() ──▶ FORSE
│
└── (scadenza) ─────▶ SCADUTO
Da ACCETTATO:
annullaPartecipazione() ──▶ ANNULLATO
NOTA: Gli organizzatori vengono impostati
automaticamente come ACCETTATO alla creazione.
招待の仕組み
参加者がイベントに追加されると、システムは次のことを行います。
- エンティティを作成する
Partecipanteステータス付き 保留中 (主催者は自動的に承認されます) - 誰が招待したかを記録します (
invitatoDaUtenteId) トレーサビリティのため - 1つを計算します 有効期限: 指定しない場合、デフォルトはイベント前日の午後 11 時 59 分 59 秒です。
- 1 つ送信する アプリ内通知 招待者の名前を受信者に送信する
- ユーザーの電子メールが認証された場合は、電子メールも送信します 電子メール通知
招待された人は、次の 3 つのオプションで応答できます。 承諾します, 拒絶 o 多分。各応答はタイムスタンプを記録します そしてオプションのメモ。応答がないまま招待の有効期限が切れた場合、スケジュールされたジョブによって招待がマークされます どうやって 期限切れ.
インテリジェントなカウンティング
システムは参加者を区別します ある e 潜在的:
contaPartecipantiConfermati()受け入れられた人のみがカウントされますcontaPartecipantiPotenziali()「たぶん」と答えた人も含まれます- 容量制限 (
maxPartecipanti)は確認済みに基づいています
チェックインとチェックアウト: ライブアクセス制御
チェックインおよびチェックアウト システムにより、時間通りにイベントへのアクセスを管理できます
実際に、分析用の貴重なデータを収集します。どちらもうまくいきます
パブリックトークン 8 文字の英数字。次の方法で生成されます。
SecureRandom 認証なしでアクセスできます。
ゲストのチェックイン
チェックインでは、出席者が到着すると人口統計データと調査データが収集されます。
主催者はパブリックリンクを生成することでチェックインを有効にできます。
(例: /c/Ab3kL9xZ)QRコードで共有可能。
CheckinInvitato
├─ nome, cognome, età
├─ città e stato di provenienza
├─ orarioArrivo (Instant.now() automatico)
├─ frequenzaPartecipazione
│ (PRIMA_VOLTA, OCCASIONALE, REGOLARE, ABITUALE)
├─ fonteEvento
│ (SOCIAL_MEDIA, AMICI, SITO_WEB, EMAIL, PASSAPAROLA...)
├─ accompagnatori
│ (DA_SOLO, COPPIA, PICCOLO_GRUPPO, GRANDE_GRUPPO)
├─ iscrizioneNewsletter (boolean)
└─ email (opzionale, richiesta se newsletter)
このデータは分析モジュールに供給され、次のような統計を計算できるようになります。 年齢層別の分布、最も効果的な獲得源、 グループ内の推定人数と初参加者の割合。
チェックアウト (イベント後のフィードバック)
チェックアウトはチェックインを補完するものであり、参加者からのフィードバックを収集します。
イベントの後。別のリンクからアクセスできます (例: /o/Xy7mN2pQ)、
以下が含まれます:
- 総合評価: 1 ~ 5 つ星。ポジティブ (4 ~ 5)、ニュートラル (3)、またはネガティブ (1 ~ 2) に分類されます。
- 良い点と改善すべき点: 事前定義されたオプション (組織、食事、場所、エンターテイメントなど) から複数選択
- 提案: 2000文字までの自由テキストフィールド
- 返品の意図: はい、たぶん、いいえ
- ネットプロモータースコア (NPS): 1 ~ 10 のスコア。推奨者 (9 ~ 10)、消極的 (7 ~ 8)、または批判者 (1 ~ 6) に分類されます。
NPS スコアの計算は、次の標準的な式に従います。 NPS = 推奨者の % - 批判者の %、重要な指標を提供します 参加者の全体的な満足度。
イベントの可視化と共有
このプラットフォームは、それぞれ独自のトークンを持つ 3 つの共有メカニズムをサポートしています。 安全に生成された 8 文字:
1. LINK DI CONDIVISIONE (/e/{token})
└─ Permette di visualizzare i dettagli dell'evento
└─ Attivabile/disattivabile dall'organizzatore
└─ Token rigenerabile (invalida il link precedente)
└─ Flag: condivisioneAttiva (boolean)
2. LINK DI CHECK-IN (/c/{token})
└─ Form pubblico per check-in senza autenticazione
└─ Raccoglie dati demografici e sondaggio
└─ Ideale per QR code stampati all'ingresso
└─ Flag: checkinAttivo (boolean)
3. LINK DI CHECKOUT (/o/{token})
└─ Form pubblico per feedback post-evento
└─ Raccoglie valutazione, NPS, suggerimenti
└─ Condivisibile via email o QR code
└─ Flag: checkoutAttivo (boolean)
さらに、イベントは 公共地図
旗を介してプラットフォームの visibileInMappa。この機能
地理座標を設定する必要があり、イベントに役立ちます
地域からの参加者を集めたい観客。
リアルタイム機能と管理
容量管理は集合体に直接実装されます
Evento。フィールド maxPartecipanti はオプションです: そうでない場合
設定すると、イベントは無制限の参加者を受け入れます。
// Verifica posti disponibili
public boolean haPostiDisponibili() {
if (this.maxPartecipanti == null) {
return true; // Nessun limite
}
return contaPartecipantiConfermati() < this.maxPartecipanti;
}
// Posti rimanenti
public Integer getPostiRimanenti() {
if (this.maxPartecipanti == null) {
return null; // Nessun limite
}
return this.maxPartecipanti - contaPartecipantiConfermati();
}
// Controllo all'aggiunta di un partecipante
public void aggiungiPartecipante(Long utenteId, RuoloPartecipante ruolo) {
// ... validazioni ...
if (this.maxPartecipanti != null &&
contaPartecipantiConfermati() >= this.maxPartecipanti) {
throw new IllegalStateException(
"Raggiunto il numero massimo di partecipanti"
);
}
Partecipante partecipante = Partecipante.crea(this.id, utenteId, ruolo);
this.partecipanti.add(partecipante);
}
カウントは RSVP 出席者のみに基づいて行われます 受け入れられました、 おそらく回答した人、またはまだ回答していない人は含まれません。これにより保証されます 容量制限は実際の確認を反映していること。
集合体はまた、楽観的ロック 野原を通して
@Version、複数の主催者が編集する際の競合を防止します。
同時にイベントも。
追加の参加者情報
役割と出欠確認ステータスに加えて、各参加者は指定できます。 組織にとって役立つ追加情報:
- 食事制限: アレルギー、不耐症、または好みについて最大 500 文字のテキストフィールド
- プラスワン: 名前を明記した同伴者の同伴も可能です。プラスワンは主催者と共催者に自動的に許可されます
- 応答メモ: RSVP 応答に添付される無料メッセージ
- 登録ステータス: フラグ
registratoとタイムスタンプregistratoIlイベントでの物理的なチェックインのため
登録された出席者の物理的なチェックイン (一般ゲストのチェックインを除く) 外部)はルールによって保護されています。招待を受け入れた人だけがアクセスできます。 チェックインを 2 回行うことはできません。
REST API
プラットフォーム イベントをプレイする イベントおよび参加者管理のための完全な RESTful API を公開しており、文書化されています。 OpenAPI/Swagger および JWT Bearer 認証経由で保護されています。
イベントエンドポイント
POST /api/v1/events Crea evento (stato DRAFT)
GET /api/v1/events/{id} Dettaglio evento
GET /api/v1/events?organizzatoreId={id} Lista eventi per organizzatore
GET /api/v1/events/all Tutti gli eventi dell'utente
?status=DRAFT&sortBy=date&sortDirection=desc
PUT /api/v1/events/{id} Aggiorna dettagli evento
DELETE /api/v1/events/{id} Elimina evento
GET /api/v1/events/statistiche Statistiche aggregate utente
--- Transizioni di Stato ---
POST /api/v1/events/{id}/publish DRAFT → PUBLISHED
POST /api/v1/events/{id}/confirm PUBLISHED → CONFIRMED
POST /api/v1/events/{id}/start CONFIRMED → IN_PROGRESS
POST /api/v1/events/{id}/start-expense-splitting
IN_PROGRESS → EXPENSE_SPLITTING
POST /api/v1/events/{id}/complete EXPENSE_SPLITTING → COMPLETED
POST /api/v1/events/{id}/cancel?motivo=... * → CANCELLED
--- Condivisione ---
POST /api/v1/events/{id}/share Genera link condivisione
GET /api/v1/events/{id}/share Stato condivisione
DELETE /api/v1/events/{id}/share Disattiva condivisione
POST /api/v1/events/{id}/share/regenerate Rigenera token
--- Check-in ---
POST /api/v1/events/{id}/checkin/enable Abilita check-in
DELETE /api/v1/events/{id}/checkin/disable Disabilita check-in
POST /api/v1/events/{id}/checkin/regenerate Rigenera token
GET /api/v1/events/{id}/checkin/status Stato check-in
GET /api/v1/events/{id}/checkin/list Lista check-in (paginata)
GET /api/v1/events/{id}/checkin/stats Statistiche check-in
--- Checkout (Feedback) ---
POST /api/v1/events/{id}/checkout/enable Abilita checkout
DELETE /api/v1/events/{id}/checkout/disable Disabilita checkout
POST /api/v1/events/{id}/checkout/regenerate Rigenera token
GET /api/v1/events/{id}/checkout/status Stato checkout
GET /api/v1/events/{id}/checkout/list Lista feedback (paginata)
GET /api/v1/events/{id}/checkout/stats Statistiche feedback
エンドポイントの参加者
GET /api/v1/events/{eventId}/participants
Lista partecipanti (arricchita con dati utente)
GET /api/v1/events/{eventId}/participants/{participantId}
Dettaglio singolo partecipante
POST /api/v1/events/{eventId}/participants
Aggiungi partecipante (con notifica e tracciamento invitante)
DELETE /api/v1/events/{eventId}/participants/{participantId}
Rimuovi partecipante (solo organizzatori)
PATCH /api/v1/events/{eventId}/participants/{participantId}/role
?nuovoRuolo=CO_ORGANIZER
Cambia ruolo partecipante
パブリック エンドポイント (認証なし)
--- Check-in Pubblico ---
GET /api/v1/public/checkin/{token}/info
Info evento per form check-in
POST /api/v1/public/checkin/{token}
Invia check-in (dati demografici + sondaggio)
--- Checkout Pubblico ---
GET /api/v1/public/checkout/{token}/info
Info evento per form feedback
POST /api/v1/public/checkout/{token}
Invia feedback (valutazione + NPS + suggerimenti)
--- Evento Pubblico ---
GET /api/v1/public/events/{token}
Visualizza dettagli evento condiviso
通知とコミュニケーション
イベントに対するすべての重要なアクションは、二重チャネルを通じて通知を生成します。
- アプリ内通知: エンティティとして作成される
NotificaUtenteイベント、通知の種類、直接リンクに関する情報 - 電子メール通知: イベントの種類ごとに特定のテンプレートを使用して、確認済みの電子メールを持つユーザーにのみ送信されます
通知の種類には次のものがあります。 招待_イベント (招待されたとき)、 EVENT_MODIFIED (詳細が変更された場合)、 EVENT_CANCELLED (キャンセル理由を添えて)e NEW_CO_ORGANIZER (昇進したとき)。すべての場合において、 アクションの作成者は受信者のリストから除外されます。
検証とビジネスルール
イベント集約は、次のような多数のドメインレベルの検証を実装します。 ビジネス ルールはコントローラーではなくモデルに存在するという DDD 原則:
- タイトル: 必須、最大 200 文字
- 日付: 終了日を開始日より前にすることはできません。ドラフトの日付はオプションです
- 座標: 指定する場合は、両方が存在する必要があります。緯度 -90 ~ 90、経度 -180 ~ 180
- 予算: 正の値でなければならず、変更可能な状態でのみ変更可能です
- 出版物: タイトルと開始日が必要です
- 参加者: イベントとユーザーのペアが一意であるため、2 番目の ORGANIZER を追加することはできません
- 容量: 限界に達したときの自動制御
このモジュールのキーパターン
- 集約ルート: すべての変更に対する単一のエントリポイントとしてのイベント
- 工場出荷時の方法:
Evento.crea()ePartecipante.crea()管理された創作用 - ステートマシン: 検証による明示的な状態遷移
- 楽観的ロック: 分野
@Version競技運営のため - 値オブジェクト:
Money予算の都合上、StatoEventoeRuoloPartecipante豊富な列挙型として - トークンベースのアクセス: 暗号的に安全なトークンを使用した共有とチェックイン
シリーズの次の記事では、 経費分割制度、 4 つの分割モードと複数通貨管理により、 イベントの参加者は、発生した費用を均等に分割します。







