調査とアンケート: 集団的な意思決定のための 2 つの補完的なシステム
イベントを企画するということは、何十もの共通の決定を下すことを意味します。 「どこに行きますか? 何を食べますか? どの日付が好きですか?」。で イベントをプレイする これらの質問は、2 つの異なる、しかし補完的なシステムを通じて答えられます。 私は アンケート 事前定義されたオプションと私に対する素早い投票のため アンケート 質問を含む構造化されたフィードバックを収集するため さまざまな種類の。これらを組み合わせることで、意見を収集するためのあらゆるニーズをカバーできます。 イベントを企画中。
この記事でわかること
- 調査ステート マシン (DRAFT、ACTIVE、CLOSED、CANCELLED)
- 最大構成可能な選択可能なオプションを備えた単一および複数の応答
- UUID 共有コードを使用した匿名投票と公開投票
- ディープコピーオプションを備えた再利用可能なテンプレート
- 各変更に対する必須の動機を含む勝者の履歴
- エンティティ別の調査 (場所、日付、メニュー、アクティビティ、カスタム)
- 入力された質問と追跡された編集を備えたアンケート システム
- リアルタイムで結果を表示
集計ルートとしてのアンケート
のドメインモデルでは、 イベントをプレイする、 の 調査 は動作が豊富な集約ルートです。 これはオプションの単純なコンテナではありません。独自のライフサイクルを管理します。 状態遷移を検証し、投票をチェックし、履歴を維持します 決定が完了しました。
@Entity
@Table(name = "sondaggi")
public class Sondaggio {
private Long id;
private Evento evento;
private TipoEntitaSondaggio tipoEntita; // LUOGO, DATA, MENU...
private Long entitaId;
private String titolo;
private String descrizione;
private TipoRispostaSondaggio tipoRisposta; // SINGOLA o MULTIPLA
private StatoSondaggio stato; // BOZZA, ATTIVO, CHIUSO, ANNULLATO
private TipoSondaggio tipo; // EVENTO o TEMPLATE
private Long templateOrigineId;
private LocalDateTime dataScadenza;
private Boolean anonimo;
private Boolean permettiModificaVoto;
private Boolean mostraRisultatiPrimaVoto;
private Integer maxOpzioniSelezionabili;
private Boolean pubblico;
private String codiceCondivisione; // UUID per sharing
private Set<OpzioneSondaggio> opzioni;
private OpzioneSondaggio opzioneVincitrice;
private Set<StoricoVincitoreSondaggio> storicoVincitori;
}
各アンケートはイベントにリンクされており、次のことを示すエンティティ タイプがあります。 何を指すのか(場所、日付、メニュー、アクティビティ)、内部で管理 your options, answers and history of winners. The Aggregate Root pattern ビジネス上のすべての不変条件が常に尊重されるようにします。
状態調査機
調査のライフサイクルは、明確に定義されたステートマシンに従います。 4 つの可能な状態と制御された遷移。あらゆるトランジションは、 ドメイン モデル自体によって検証されるため、無効な状態に到達することができなくなります。
public enum StatoSondaggio {
BOZZA("Bozza"), // Creazione e configurazione
ATTIVO("Attivo"), // Aperto alle votazioni
CHIUSO("Chiuso"), // Votazioni terminate
ANNULLATO("Annullato"); // Sondaggio invalidato
}
許可される遷移は次のとおりです。
- ドラフト→アクティブ (
pubblica()): 少なくとも 2 つのオプションが必要です - 活動中 → 閉鎖中 (
chiudi()): 投票を終了します - アクティブ → キャンセル済み (
annulla()): アンケートを無効にします - クローズ → アクティブ (
riapri()): 投票を再開します
public void pubblica() {
if (this.stato != StatoSondaggio.BOZZA) {
throw new IllegalStateException(
"Solo sondaggi in bozza possono essere pubblicati");
}
if (this.opzioni.size() < 2) {
throw new IllegalStateException(
"Il sondaggio deve avere almeno 2 opzioni");
}
this.stato = StatoSondaggio.ATTIVO;
}
public void riapri() {
if (this.stato != StatoSondaggio.CHIUSO) {
throw new IllegalStateException(
"Solo sondaggi chiusi possono essere riaperti");
}
this.stato = StatoSondaggio.ATTIVO;
}
なぜ再開なのか?
CLOSED → ACTIVE への移行は重要な設計上の選択です。 現実の生活では、意思決定が変わります。場所が利用できなくなり、 投票したい、または単に奉仕したい新しい参加者が到着します 決定を下すにはさらに多くの投票が必要です。新しいアンケートを作成する代わりに 既存の投票は失われますが、再開すると収集を続行できます 透明な方法で。
応答タイプ: SINGLE および MULTIPLE
The system supports two voting modes covering scenarios イベント企画で最も一般的なもの。
public enum TipoRispostaSondaggio {
SINGOLA("Singola"), // Radio button - una sola scelta
MULTIPLA("Multipla"); // Checkbox - più' opzioni selezionabili
}
返答の場合 シングル、フィールド maxOpzioniSelezionabili
は自動的に 1 に設定されます。 複数、
デフォルト値は 3 ですが、主催者が設定できます。
これにより、「10 個の提案からお気に入りのアクティビティを 3 つ選択する」などのシナリオが可能になります。
public static Sondaggio crea(Evento evento, User creatoDa,
TipoEntitaSondaggio tipoEntita, String titolo,
TipoRispostaSondaggio tipoRisposta) {
Sondaggio s = new Sondaggio();
// ... inizializzazione campi ...
s.maxOpzioniSelezionabili =
tipoRisposta == TipoRispostaSondaggio.SINGOLA ? 1 : 3;
s.permettiModificaVoto = true;
s.mostraRisultatiPrimaVoto = false;
return s;
}
匿名投票
各アンケートは次のように構成できます。 匿名。
匿名性が有効な場合、応答は匿名性なしで記録されます
それらを投票ユーザーと関連付け、機密性の高い決定におけるプライバシーを保証します。
フィールド anonimo に設定されています false デフォルトでは:
主催者は明示的にこれを有効にする必要があります。
匿名応答はファクトリメソッドを使用します creaAnonima()
ユーザーも電子メールも必要ありません。追跡
二重投票の防止は、次の方法で個別に処理されます。 ブラウザの指紋.
// Voto di un utente registrato
RispostaSondaggio.creaPerUtente(sondaggio, opzione, utente);
// Voto di un esterno via email (con token di modifica)
RispostaSondaggio.creaPerEmail(sondaggio, opzione, email);
// Voto anonimo su sondaggio pubblico
RispostaSondaggio.creaAnonima(sondaggio, opzione);
共有コードを使用した公開アンケート
アンケートができる 公共 集める プラットフォームに登録していない人からも投票できます。システムは、 UUID共有コード できるだけユニークなものに リンクとして配布されています。
public String rendiPubblico() {
if (this.stato == StatoSondaggio.BOZZA) {
throw new IllegalStateException(
"Pubblica il sondaggio prima di renderlo pubblico");
}
if (this.pubblico && this.codiceCondivisione != null) {
return this.codiceCondivisione; // Gia' pubblico
}
this.pubblico = true;
this.codiceCondivisione = UUID.randomUUID().toString();
return this.codiceCondivisione;
}
public String rigeneraCodiceCondivisione() {
// Invalida i link precedenti
this.codiceCondivisione = UUID.randomUUID().toString();
return this.codiceCondivisione;
}
実体 一般投票 世論調査での匿名の投票を追跡します
公共の。を使用します。 ブラウザの指紋 防ぐために
重複投票: 一意性制約 (sondaggio_id, fingerprint)
各デバイスが 1 回だけ投票できるようにします。
@Entity
@Table(name = "voti_pubblici",
uniqueConstraints = @UniqueConstraint(
columnNames = {"sondaggio_id", "fingerprint"}))
public class VotoPubblico {
private Long id;
private Sondaggio sondaggio;
private String fingerprint; // Hash del browser
private Instant votatoIl;
}
公開調査の流れ
- 主催者がアンケートを作成して公開します (ACTIVE ステータス)
- 呼び出す
rendiPubblico()UUIDコードを生成するには - リンクはメッセージ、電子メール、ソーシャルメディア経由で共有されます
- 部外者は登録なしで投票し、指紋で追跡される
- 主催者はコードを再生成して以前のリンクを無効にすることができます
- アンケートを非公開にし、コードを保持したまま再度有効にすることができます
再利用可能なテンプレート
システムはタイプ調査を区別します イベント (に接続します 特定のイベント)およびタイプ調査 テンプレート (再利用可能)。 テンプレートは、どのイベントにもリンクされていないアンケートです。 テンプレートを使用して、異なるコンテキストで同一のインスタンスを作成します。
// Crea un template riutilizzabile
Sondaggio template = Sondaggio.creaTemplate(
utente, "Preferenza Location",
TipoEntitaSondaggio.LUOGO, "Dove facciamo la festa?",
TipoRispostaSondaggio.SINGOLA);
// Istanzia il template per un evento specifico
Sondaggio istanza = Sondaggio.creaDaTemplate(
template, evento, organizzatore);
istanza.copiaOpzioniDa(template);
// L'istanza eredita: titolo, descrizione, tipoRisposta,
// configurazione (anonimo, maxSelezionabili, ecc.)
// ma parte sempre in stato BOZZA
オプションのコピーは、インスタンス化の維持とは別のものです
正しい JPA 関係。各オプションは次のように複製されます。
OpzioneSondaggio.copiaDa() 新しいエンティティを作成します
同じデータを持つが ID なし、応答なし、インスタンスを保証する
テンプレートから完全に独立しています。
監査証跡付きの受賞者の履歴
型式調査用 場所、システムは選択をサポートします 投票終了後に勝利の選択肢が決まります。それぞれの選択または 勝者の変更はエンティティ内で追跡されます 歴史勝者アンケート、完全な監査証跡を作成します。
@Entity
@Table(name = "storico_vincitori_sondaggio")
public class StoricoVincitoreSondaggio {
private Long id;
private Sondaggio sondaggio;
private OpzioneSondaggio opzionePrecedente; // null se prima selezione
private OpzioneSondaggio opzioneNuova;
private String motivazione; // obbligatoria per cambi
private User modificatoDa;
private Instant modificatoIl;
}
重要なルールは次のとおりです。 モチベーションは必須です すでに選択されている当選者を変更する場合。第一次選考対象外 が必要です。これにより、グループの決定における透明性が確保されます。
public StoricoVincitoreSondaggio selezionaVincitore(
Long opzioneId, User selezionatoDa, String motivazione) {
verificaChiuso(); // Deve essere CHIUSO
verificaTipoLuogo(); // Solo per sondaggi LUOGO
OpzioneSondaggio opzione = trovaNelleOpzioni(opzioneId);
StoricoVincitoreSondaggio storico;
if (this.opzioneVincitrice != null) {
// Cambio vincitore: motivazione OBBLIGATORIA
if (motivazione == null || motivazione.isBlank()) {
throw new IllegalArgumentException(
"La motivazione e' obbligatoria per cambiare il vincitore");
}
storico = StoricoVincitoreSondaggio.creaCambio(
this, this.opzioneVincitrice, opzione,
motivazione, selezionatoDa);
} else {
// Prima selezione
storico = StoricoVincitoreSondaggio.creaPrimaSelezione(
this, opzione, selezionatoDa);
}
this.opzioneVincitrice = opzione;
this.storicoVincitori.add(storico);
return storico;
}
エンティティ調査: 単純な質問を超えて
調査は一般的なものではありません。各調査は、 エンティティタイプ それはそのコンテキストを定義し、 利用可能な機能。
public enum TipoEntitaSondaggio {
LUOGO("Luogo"), // Votare la venue dell'evento
DATA("Data"), // Scegliere la data
MENU("Menu"), // Preferenze catering
ATTIVITA("Attività"), // Attività da organizzare
CUSTOM("Personalizzato"); // Sondaggio generico
}
その男 場所 特に豊富なオプション:
システム内のエンティティを配置するには、次の方法を使用します。
entitaRiferimentoId、説明と専用の画像付き。
さらに、勝者の選択をサポートしているのは LUOGO 投票のみです。
@Entity
public class OpzioneSondaggio {
private Long id;
private Sondaggio sondaggio;
private String testo;
private Long entitaRiferimentoId; // ID del luogo collegato
private String descrizione; // Info aggiuntive
private String immagineUrl; // Foto della venue
private Integer ordine;
private Set<RispostaSondaggio> risposte;
}
専用のファクトリーメソッド creaPerLuoghi() 作成を簡素化します
場所を選択するための調査、エンティティ タイプの事前設定
PLACEへの応答とSINGLEへの応答。
設定可能なオプション
各投票には、投票エクスペリエンスを制御する 3 つのブール フラグが用意されています。 これらの設定は、調査が行われている間のみ編集可能です。 ドラフト状態です。
- maxSelectableOptions: 選択肢の数を制限します。 複数の回答。少なくとも 1 であることが検証されます。
-
showResult最初の投票: もし
true、参加者 投票する前に部分的な結果を確認してください。デフォルト:falseのために バンドワゴン効果を避ける。 -
許可編集評価: もし
true、有権者はできる 選択を変えてください。デフォルト:true.
public void configura(Boolean anonimo, Boolean permettiModificaVoto,
Boolean mostraRisultatiPrimaVoto,
Integer maxOpzioniSelezionabili,
LocalDateTime dataScadenza) {
verificaModificabile(); // Solo in stato BOZZA
if (maxOpzioniSelezionabili != null) {
if (maxOpzioniSelezionabili < 1) {
throw new IllegalArgumentException(
"Deve essere selezionabile almeno un'opzione");
}
this.maxOpzioniSelezionabili = maxOpzioniSelezionabili;
}
this.dataScadenza = dataScadenza;
// ... altri campi ...
}
La 有効期限 is optional: if set, the survey
投票期限が過ぎた場合でも、投票の受け付けは自動的に停止されます。
ACTIVE status.方法 isVotabile() 両方の条件を確認してください。
アンケートシステム
投票と同時に、 イベントをプレイする のシステムを提供しています アンケート 情報を収集する より構造化されたもの。世論調査は選択肢に対する投票ですが、 事前に定義されているように、アンケートはさまざまな種類の質問が含まれるフォームです。
@Entity
@Table(name = "questionari")
public class Questionario {
private Long id;
private Evento evento;
private TipoQuestionario tipo; // TEMPLATE o EVENTO
private Long templateOrigineId;
private String titolo;
private String descrizione;
private String etichetta;
private String coloreEtichetta;
private StatoQuestionario stato; // BOZZA, PUBBLICATO, CHIUSO, ARCHIVIATO
private LocalDateTime dataScadenza;
private Boolean anonimo;
private Boolean richiediEmail;
private Boolean richiediNome;
private Boolean pubblico;
private String codiceCondivisione;
private List<DomandaQuestionario> domande;
}
アンケートのライフサイクルはアンケートのライフサイクルと似ていますが、 もう 1 つの状態: ドラフト → 公開 → 終了 → アーカイブ済み。アーカイブ済みステータスでは、アンケートを保存できます アクティブなビューを乱雑にすることなく完了しました。
public enum StatoQuestionario {
BOZZA("Bozza"), // In fase di creazione
PUBBLICATO("Pubblicato"), // Aperto alle compilazioni
CHIUSO("Chiuso"), // Compilazioni terminate
ARCHIVIATO("Archiviato"); // Conservato in archivio
}
アンケートの質問タイプ
アンケートの各質問には、質問の表示方法を決定するタイプがあります。 回答がどのように収集されるか。
public enum TipoDomanda {
APERTA("Risposta aperta"), // Testo libero
SCELTA_SINGOLA("Scelta singola"), // Radio button
SCELTA_MULTIPLA("Scelta multipla"); // Checkbox
}
タイプの質問 開ける フリーテキストを収集: コメント、
提案、特別なメモ。に対する質問は、 SINGLE_CHOICE e
MULTIPLE_CHOICE によるデフォルトのオプションが必要です
実体 OpzioneDomanda。システムが自動的に検証します
オプションが必要なタイプのみがオプションを使用できるということです。
@Entity
public class DomandaQuestionario {
private Long id;
private Questionario questionario;
private String testo;
private TipoDomanda tipoDomanda;
private Boolean obbligatoria; // Default: true
private Integer ordine;
private String descrizioneAiuto; // Testo di aiuto opzionale
private List<OpzioneDomanda> opzioni;
}
// Validazione nel metodo aggiungiOpzione
public OpzioneDomanda aggiungiOpzione(String testo) {
if (!this.tipoDomanda.richiedeOpzioni()) {
throw new IllegalStateException(
"Le domande di tipo " + this.tipoDomanda
+ " non supportano opzioni");
}
// ...
}
コンパイルと応答
ユーザーがアンケートに記入すると、システムは アンケート集計 すべての答えをグループ化します。 アンケートは登録ユーザーによる集計と集計の両方に対応しています。 公開(匿名または自発的に提供されたデータ付き)。
@Entity
public class CompilazioneQuestionario {
private Long id;
private Questionario questionario;
private User utente; // null se compilazione pubblica
private String nomeCompilatore;
private String emailCompilatore;
private String fingerprint; // Per tracciamento anonimo
private List<RispostaQuestionario> risposte;
private Instant compilatoIl;
}
// Due modalità' di compilazione
CompilazioneQuestionario.creaPerUtente(questionario, utente);
CompilazioneQuestionario.creaPubblica(
questionario, nome, email, fingerprint);
それぞれの応答は次のように形成されます。 回答アンケート、それ 自由記述 (自由な質問用) と選択されたオプションの両方を管理します (選択質問の JSON 配列としてシリアル化されます)。
レーベルと組織
アンケートは次のように分類できます。 ラベル カスタマイズ可能な名前と色。システムは事前定義されたラベルを提供します (システム) と各ユーザーがパーソナライズされたものを作成する可能性。
@Entity
public class EtichettaQuestionario {
private Long id;
private String nome; // Max 50 caratteri
private String colore; // Codice esadecimale (#3B82F6)
private Long userId; // null per etichette di sistema
private boolean predefinita; // true = di sistema
}
// Creazione etichetta personalizzata
EtichettaQuestionario.creaPersonalizzata(
userId, "Feedback Post-Evento", "#10B981");
リアルタイムの結果
結果の可視性は、両方の正確なルールによって制御されます。
調査とアンケート。調査の場合、その方法は、
sonoRisultatiVisibili() ロジックを実装します。
- 調査が 閉店: 結果は常に全員に表示されます
- Se showResult最初の投票 アクティブ: 投票しなくても結果が表示されます
- それ以外の場合: 結果は投票後にのみ表示されます
public boolean sonoRisultatiVisibili(boolean haVotato) {
if (this.stato == StatoSondaggio.CHIUSO) return true;
if (Boolean.TRUE.equals(this.mostraRisultatiPrimaVoto)) return true;
return haVotato;
}
public boolean isVotabile() {
return this.stato == StatoSondaggio.ATTIVO &&
(this.dataScadenza == null ||
LocalDateTime.now().isBefore(this.dataScadenza));
}
結果は、各オプションの回答を集計することによって計算されます。
すべて順調です OpzioneSondaggio カウントを公開します
getNumeroVoti() コレクション内の応答をカウントします。
フロントエンドでは、このデータが更新された棒グラフとパーセント グラフにフィードされます。
リアルタイムで。
重要なポイント
- 4 つの制御された遷移と再開の可能性を備えたステート マシン
- 選択可能なオプションの制限を構成可能な単一および複数の応答
- 重複防止のための指紋追跡による匿名投票
- 外部有権者向けの UUID 共有コードを使用した世論調査
- オプションを備えたディープコピーを備えた再利用可能なテンプレート
- 勝者の変更ごとに正当な理由を記載した必須の監査証跡
- 5種類の調査エンティティ(LOCATION、DATE、MENU、ACTIVITY、CUSTOM)
- 質問が入力されたアンケート (OPEN、SINGLE_CHOICE、MULTIPLE_CHOICE)
- アンケートを整理するためのカスタマイズ可能なカラーラベル
- 3 つのアクセス レベルによる制御された結果の可視性
プロジェクトのソースコードは次の場所から入手できます。 GitHub。 完全な調査およびアンケート システムを試すには、次のサイトにアクセスしてください。 www.playtheevent.com.







