組織とフェスティバル: 複雑なイベントのための複雑な構造
単一のユーザーでは十分ではない場合、 組織。で イベントをプレイする 組織はできる代理店 または協会、 それぞれに独自のプロフィール、独自のメンバー、独自の役割があります。そしていつ 組織は、部屋、スタンド、スケジュールを含む数日間にわたるイベントを管理したいと考えています 明確に表現されると、モジュールが機能します 祭り.
この記事では、 イベントをプレイする 単一のイベントの管理からイベントの管理まで拡張する 数十人の俳優が参加する数日間のイベント。
この記事でわかること
- 会社と協会の 2 つのタイプの組織
- 双方向ワークフローによるメンバーシップと階層的役割
- まだ登録していないユーザーへの招待メール
- 複数日にわたるイベントの集約ルートとしてのフェスティバル
- 曜日、部屋、スタンドとそのオーケストレーション
- ライフサイクルを備えたスタンド予約システム
- 予算管理のための Money Value オブジェクト
2 つのタイプの組織
プラットフォームは enum を介して 4 つのアカウント タイプをサポートします。 アカウントの種類:個人、企業、団体 SUPER_ADMINISTRATOR。最初の 3 つは登録時に選択できます。 ユーザーが COMPANY または ASSOCIATION を選択すると、システムは 特定の追加プロファイルのコンパイル。
TipoAccount
├── INDIVIDUO → Nessun profilo aggiuntivo
├── AZIENDA → Richiede ProfiloAzienda
├── ASSOCIAZIONE → Richiede ProfiloAssociazione
└── SUPER_AMMINISTRATORE → Non selezionabile in registrazione
会社概要
企業には国際税務データが満載のプロフィールがあります: 企業名、 国 (ISO 3166-1 alpha-2 コード)、VAT 番号、納税者 ID、および登録番号 ビジネス。連絡先の詳細 (会社の電子メール、PEC)、登録オフィスも含まれます。 値オブジェクト経由 住所、会社の連絡先、および それが属するセクター。
@Entity
public class ProfiloAzienda {
private Long id;
private User user; // Relazione 1:1
// Identificazione
private String ragioneSociale; // "TechCorp S.r.l."
private String paese; // "IT" (ISO 3166-1 alpha-2)
private String vatNumber; // "IT12345678901"
private String taxId; // Codice Fiscale
private String companyRegistrationNumber;
// Contatti
private String emailAziendale;
private String pec; // Per aziende italiane
// Sede e referente
private Indirizzo sedeLegale; // Value Object embedded
private String referenteNome;
private String referenteCognome;
private String referenteRuolo;
private SettoreAzienda settore; // Classificazione
}
協会概要
これらの協会は、非営利の世界を指向した異なるプロフィールを持っています。 キーフィールドと タイプ関連付けに応じて異なります。 国向け: イタリアの場合は、APS、ASD、ODV、ETS、ONLUS をサポートします。米国向け 501(c)(3);英国慈善団体のために。各タイプは、 選択した国。
@Entity
public class ProfiloAssociazione {
private Long id;
private User user; // Relazione 1:1
private String nomeAssociazione; // "ASD Runners Bari"
private String paese; // "IT"
private TipoAssociazione tipoAssociazione; // APS, ASD, ODV, 501C3...
private String registrationNumber; // RUNTS, EIN, etc.
private String taxId;
private Indirizzo sede;
private String rappresentanteLegaleNome;
private String rappresentanteLegaleCognome;
}
// Validazione tipo-paese:
// TipoAssociazione.ASD.isValidoPerPaese("IT") → true
// TipoAssociazione.ASD.isValidoPerPaese("US") → false
// TipoAssociazione._501C3.isValidoPerPaese("US") → true
2 つのプロファイルの違い
- 会社概要: 完全な税務データ (VAT、PEC、会社登記簿)、会社の連絡先、所属する部門、事業計画
- 協会プロフィール: 国によって検証された協会の種類、法定代理人、登録番号 (RUNTS/EIN)、35 ~ 38% 割引の ASSOCIATION プラン
メンバーシップと役割: MembershipOrganization
COMPANY または ASSOCIATION アカウントを持つユーザーは、組織 他のユーザーが所属できるもの。組織とメンバーの関係 そして実体によって形づくられる 会員組織、機能します メンバーシップ サブドメインの集約ルートから。
階層的な役割
ロールは列挙型によって定義されます 役割組織 4つで 認可レベルの増加。
public enum RuoloOrganizzazione {
MEMBRO(10), // Partecipa e riceve benefici piano
RESPONSABILE(50), // Crea eventi/viaggi per l'organizzazione
ADMIN(80), // Gestisce membri e ruoli
OWNER(100); // Controllo completo (uno solo per org)
}
PERMESSI:
MEMBRO RESPONSABILE ADMIN OWNER
Ricevere benefici ✓ ✓ ✓ ✓
Creare eventi ✗ ✓ ✓ ✓
Gestire membri ✗ ✗ ✓ ✓
Cambiare ruoli ✗ ✗ ✓ ✓
Invitare membri ✗ ✗ ✓ ✓
Rimuovere membri ✗ ✗ ✓ ✓
役割 所有者 および特殊: は自動的に割り当てられます
組織の作成者に割り当てられ、招待によって割り当てられることはできません。
一時停止することはできません。所有権を譲渡するには専用の方法があります
trasferisciOwnership().
双方向ワークフロー
メンバーシップは 2 つのストリームをサポートします: 組織 招待する メンバー (初期状態 PENDING_INVITO) またはメンバー 必要 参加します (初期状態は PENDING_REQUEST)。 どちらの場合も、受信者は受け入れるか拒否することができます。
public enum StatoAppartenenza {
PENDING_INVITO, // L'org ha invitato, in attesa del membro
PENDING_RICHIESTA, // Il membro ha richiesto, in attesa dell'org
ATTIVO, // Membro attivo nell'organizzazione
SOSPESO, // Temporaneamente sospeso
RIFIUTATO // Invito/richiesta rifiutata
}
FLUSSO INVITO (dall'organizzazione):
PENDING_INVITO ──accetta──► ATTIVO ──sospendi──► SOSPESO
│ │
└──rifiuta──► RIFIUTATO riattiva ◄───┘
FLUSSO RICHIESTA (dal membro):
PENDING_RICHIESTA ──approva──► ATTIVO
│
└──rifiuta──► RIFIUTATO
システムもサポートしています マルチメンバーシップ: ユーザー 同時に複数の組織に所属し、異なる役割を持つことができます それぞれに。テーブルにはペアの一意性制約があります (organization_id, member_id) 重複を避けるため。
電子メールによる招待: OrganizationInvitationEmail
プラットフォームにまだ登録していない人を招待するにはどうすればよいですか?を通して 招待状組織メール。このエンティティは全体を管理します フロー: 固有のトークンを含むメールの送信から変換まで ユーザーが同意すると、MembershipOrganization が返されます。
1. Admin/Owner crea invito con email destinatario
2. Sistema genera token SecureRandom (48 bytes, Base64 URL-safe)
3. Email inviata con link contenente il token
4. Destinatario clicca il link
├── Se registrato → Accetta direttamente
└── Se non registrato → Si registra, poi accetta
5. Invito convertito in AppartenenzaOrganizzazione ATTIVA
STATI INVITO:
PENDING ──accetta──► ACCETTATO
│
├──annulla──► ANNULLATO (dall'org)
├──scade────► SCADUTO (dopo N giorni, default 7)
└──rinnova──► PENDING (nuovo token, nuova scadenza)
招待状のセキュリティ
各招待状は、暗号的に安全な 48 バイトのトークンを生成します。 セキュアランダム。トークンには構成可能な有効期限があります (デフォルトでは 7 日間)、招待が更新されると再生成されます。メールが来る 送信前に正規化(小文字、トリミング)され、検証されます。
フェスティバル: 複数日にわたるイベントの集約ルート
さて、この記事の 2 番目の大きなテーマに移りましょう。あ 祭り in イベントをプレイする 複数日にわたるイベントを日数でモデル化する複雑な集計 プログラムされた設備の整った部屋、展示スタンド、そして体系化されたプログラム。 DDD 原則に従って、フェスティバルと、境界を持つ集約ルート 強い一貫性: すべての変更がそれを通過します。
Festival (Aggregate Root)
├── id, titolo, descrizione
├── organizzatoreId // Chi organizza
├── luogoId // Location principale
├── dataInizio / dataFine
├── stato: StatoFestival
├── budget: Money // Value Object embedded
├── maxPartecipantiTotali
├── tokenCondivisione // QR / link sharing
│
├── giornate: Set<GiornataFestival>
│ └── ogni giornata ha i suoi EventoFestival
│
├── sale: Set<SalaFestival>
│ └── ogni sala ha le sue ConfigurazioneSala
│
└── stand: Set<StandFestival>
└── ogni stand ha le sue PrenotazioneStand
フェスティバルのライフサイクル
フェスティバルは、制御された移行を伴って、明確に定義された 5 つの州を通過します。 各ステップでの検証。
public enum StatoFestival {
DRAFT, // Bozza: in preparazione, non visibile
PUBLISHED, // Pubblicato: visibile, in attesa dell'inizio
IN_CORSO, // In svolgimento
COMPLETATO, // Concluso con successo
ANNULLATO // Annullato
}
TRANSIZIONI:
DRAFT ──pubblica──► PUBLISHED ──avvia──► IN_CORSO ──completa──► COMPLETATO
│ │
└──annulla──► ANNULLATO ◄──annulla───┘
▲
DRAFT ────────────────annulla────────────┘
PERMESSI PER STATO:
DRAFT PUBLISHED IN_CORSO COMPLETATO ANNULLATO
Modificabile ✓ ✓ ✗ ✗ ✗
Accetta eventi ✓ ✓ ✓ ✗ ✗
Accetta prenotaz. ✓ ✓ ✗ ✗ ✗
Annullabile ✓ ✓ ✓ ✗ ✗
フェスティバルを公開するには、少なくとも 1 日必要です
設定されています。システムはメソッド内でこの要件をチェックします。
pubblica() 満たされない場合は例外をスローします。
お祭りの日
すべて順調です 祭りの日 の 1 日を表します 祭り。日付、オプションのタイトル、開始時間と終了時間があります。 (デフォルトは 09:00 ~ 23:00) およびプログレッシブ順序。
@Entity
public class GiornataFestival {
private Long id;
private Long festivalId;
private LocalDate data;
private String titolo; // "Giorno 1 - Apertura"
private String descrizione;
private LocalTime oraInizio; // Default: 09:00
private LocalTime oraFine; // Default: 23:00
private Integer ordine; // Posizione nel calendario
private Set<EventoFestival> eventi; // Eventi programmati
}
VALIDAZIONI:
- La data deve essere compresa tra dataInizio e dataFine del festival
- Non possono esistere due giornate con la stessa data
- Gli orari degli eventi devono rientrare negli orari della giornata
- Non si può rimuovere una giornata con eventi programmati
フェスティバルでは、次のような方法も提供します。 generaGiornate() それが生み出す
期間中の毎日が自動的に祭りの日になるため、
既存の日と重複します。
ホール: SalaFestival と構成
Le サラフェスティバル それらはフェスティバルの物理的空間を表します。 各部屋には、その機能と用途を決定するタイプがあります。
public enum TipoSala {
SALA_CONFERENZE // Conferenze e presentazioni (seduti, eventi)
AUDITORIUM // Grande sala per molti partecipanti (seduti)
PALCO // Spettacoli e performance (eventi)
AREA_ESTERNA // Spazio all'aperto (eventi)
STAND_AREA // Area dedicata agli stand espositivi
SALA_WORKSHOP // Workshop pratici (seduti, eventi)
FOYER // Area accoglienza e networking
SALA_STAMPA // Per giornalisti e media (seduti, eventi)
}
capacità:
Posti Seduti Ospita Eventi
SALA_CONFERENZE ✓ ✓
AUDITORIUM ✓ ✗
PALCO ✗ ✓
AREA_ESTERNA ✗ ✓
STAND_AREA ✗ ✗
SALA_WORKSHOP ✓ ✓
FOYER ✗ ✗
SALA_STAMPA ✓ ✓
部屋の構成
部屋にできるのは、 複数の構成 容量あり 違う。たとえば、会議室は次のモードでセットアップできます。 「シアター」(着席200席)、「プラテア」(着席150席+立席50席)または 「スタンディング」(スタンディング300人)。構成をアクティブ化することも、 非アクティブ化されました。
SalaFestival: "Sala Magna" (SALA_CONFERENZE)
├── Configurazione "Teatro"
│ ├── Posti seduti: 200
│ ├── Posti in piedi: 0
│ └── Attiva: true
├── Configurazione "Platea"
│ ├── Posti seduti: 150
│ ├── Posti in piedi: 50
│ └── Attiva: true
└── Configurazione "Standing"
├── Posti seduti: 0
├── Posti in piedi: 300
└── Attiva: false
Capienza massima: max(200, 200, 300) = 300 (tra configurazioni attive: 200)
スタンド: StandFestival と予約
Gli スタンドフェスティバル 展示空間を表現します。の 基本的な違いとスタンド間の違い 自分の (管理された 組織による) レンタル可能 (第三者へのレンタル可能 日割り価格あり)。
StandFestival
├── codice: "A01" // Identificativo univoco
├── nome: "Stand Principale"
├── tipo: PROPRIO | AFFITTABILE
├── dimensioniMq: 25.50
├── prezzoGiornaliero: Money // Solo per AFFITTABILE
├── dotazioni: "Tavolo, sedie, corrente elettrica"
├── posizioneMappa: "Padiglione A, Fila 1"
│
└── prenotazioni: Set<PrenotazioneStand>
└── PrenotazioneStand
├── giornataId // Per quale giorno
├── affittuarioNome // Chi affitta
├── affittuarioEmail
├── attivitaNome // Cosa esporra
├── stato: StatoPrenotazione
└── importoPagato: Money // Tracking pagamenti
予約のライフサイクル
すべての予約はシンプルですが効果的なサイクルを経ます。スタンド缶 一意性の制約付きで、特定の日に予約することはできません。 同じスタンドには、同じスタンドに対して 2 つのアクティブな予約が存在する可能性があります。 日。
public enum StatoPrenotazione {
PRENOTATO, // Prenotazione effettuata, in attesa di conferma
CONFERMATO, // Prenotazione confermata e attiva
ANNULLATO // Prenotazione annullata
}
TRANSIZIONI:
PRENOTATO ──conferma──► CONFERMATO
│ │
└──annulla──► ANNULLATO ◄┘
VINCOLI:
- Solo stand AFFITTABILE possono essere prenotati
- Un solo affittuario per stand per giornata
- La prenotazione include tracking dei pagamenti via Money
EventtoFestival: イベント-当日-ホールのつながり
実体 イベントフェスティバル イベントとイベントの架け橋となる 祭りの文脈。イベントを特定の日にリンクするには、 正確な時刻と部屋へのオプションの割り当て。システムがチェックする 同じ部屋に重複がないことが自動的に確認されます。
public enum TipoEventoFestival {
// Eventi principali (main stage)
MAIN_EVENT, KEYNOTE, CERIMONIA, CONCERTO, SPETTACOLO
// Eventi formativi
TALK, WORKSHOP, PANEL
// Networking e servizi
NETWORKING, REGISTRAZIONE, BREAK, ALTRO
}
ESEMPIO PALINSESTO - Giorno 1:
09:00-09:30 REGISTRAZIONE (Foyer)
09:30-10:30 CERIMONIA apertura (Auditorium)
10:30-11:00 BREAK (Foyer)
11:00-12:00 KEYNOTE speaker (Auditorium)
11:00-12:30 WORKSHOP Angular (Sala Workshop A)
11:00-12:30 WORKSHOP Spring Boot (Sala Workshop B)
12:30-14:00 BREAK pranzo
14:00-14:45 TALK microservizi (Sala Conferenze 1)
14:00-14:45 TALK frontend (Sala Conferenze 2)
15:00-16:30 PANEL "Il futuro del web" (Auditorium)
17:00-18:00 NETWORKING (Area Esterna)
21:00-23:00 CONCERTO (Palco)
オーバーラップ制御
イベントがルームに割り当てられると、システムはそれが割り当てられていないことを確認します。
同じ部屋の他のイベントと時間的に重複します。
2 つのイベントが重なる場合 A.oraInizio < B.oraFine AND
A.oraFine > B.oraInizio。部屋が割り当てられていないイベントでは、
チェックされています。
値オブジェクト Money を使用した予算
フェスティバルもスタンドもバリューオブジェクトを使用 お金
金額を管理するため。お金はカプセル化された不変のオブジェクトです。
金額 (BigDecimal スケール付き 2) と通貨
(String ISO 4217)。
@Embeddable
public class Money implements Comparable<Money> {
private BigDecimal amount; // Scala 2, HALF_UP
private String currency; // ISO 4217 (EUR, USD, GBP...)
// Factory methods
Money.of(100.00, "EUR")
Money.euro(50.00)
Money.zero("EUR")
// Operazioni aritmetiche (stessa valuta obbligatoria)
money1.add(money2) // Somma
money1.subtract(money2) // Sottrazione
money1.multiply(1.22) // Moltiplicazione (es: IVA)
money1.divide(3) // Divisione
// Query
money.isPositive()
money.isZero()
money.isGreaterThan(other)
}
USO NEL FESTIVAL:
Festival.budget = Money.euro(50000.00) // Budget totale
StandFestival.prezzoGiornaliero = Money.euro(150.00) // Prezzo affitto/giorno
PrenotazioneStand.importoPagato = Money.euro(450.00) // 3 giorni x 150
お金を値オブジェクトとして使用すると、追加できないことが保証されます 異なる通貨での金額 (システムは例外をスローします)、およびすべて 丸めは一貫しています。
フェスティバルを共有する
各フェスティバルでは、 共有トークン (8 英数字)を使用して、フェスティバルをすばやく共有できます リンクまたはQRコードから。共有はオンとオフを切り替えることができます 主催者により随時。
重要なポイント
- 国固有の検証を備えた 2 つの組織プロファイル (会社および協会)
- 4 つの階層的な役割と双方向の招待/リクエスト ワークフローを備えた MembershipOrganization
- OrganizationInvitationEmail は安全なトークンと有効期限を指定して未登録ユーザーを招待します
- ライフサイクルに 5 つの状態がある集約ルート DDD のようなフェスティバル
- 設定可能な時間と自動生成を備えた DayFestival
- 複数の構成と容量制御を備えた 8 つの部屋タイプ
- 1日ごとの予約システムを備えた独自のレンタルスタンド
- 部屋ごとにオーバーレイ制御による12種類のフェスティバルイベント
- 予算管理とタイプセーフな支払いのための Money Value オブジェクト
ソースコードは次の場所から入手できます。 GitHub。 実際の組織やフェスティバルの運営について詳しく知りたい場合は、次のサイトをご覧ください。 www.playtheevent.com.







