Use Case EventStorming Diagrams#

EventStorming diagrams for every use case discovered from the Gherkin scenarios in specs/ansible_modules and specs/ansible_playbook: the Ansible Operator issues a command, an aggregate applies it, and the aggregate produces an event (or a read model, for the two surfaces that never change Exasol state) or a failure.

Overview#

One representative branch per use case. Detailed per-use-case diagrams below expand every command/event branch a use case’s scenarios exercise.

        flowchart LR
    operator(["Ansible Operator"])

    subgraph gather_server_info["gather-server-info (exasol_info)"]
        direction TB
        CMD_GatherInfo["GatherServerInfo<br/>(Command)"]:::command
        AGG_ServerInfo["SERVER_INFO<br/>(Read Model)"]:::aggregate
        EVT_InfoReported["ServerInfoReported<br/>(Read Model, no state change)"]:::readmodel
        CMD_GatherInfo --> AGG_ServerInfo --> EVT_InfoReported
    end

    subgraph execute_query_batch_ov["execute-query-batch (exasol_query)"]
        direction TB
        CMD_ExecuteQueryBatch["ExecuteQueryBatch<br/>(Command)"]:::command
        AGG_QueryExecution["QUERY_EXECUTION<br/>(Aggregate)"]:::aggregate
        EVT_QueryBatchExecuted["QueryBatchExecuted<br/>(Event)"]:::event
        CMD_ExecuteQueryBatch --> AGG_QueryExecution --> EVT_QueryBatchExecuted
    end

    subgraph execute_bound_query_ov["execute-bound-query (exasol_query)"]
        direction TB
        CMD_ExecuteBoundQuery["ExecuteBoundQuery<br/>(Command)"]:::command
        AGG_BoundQueryExecution["QUERY_EXECUTION<br/>(Aggregate)"]:::aggregate
        EVT_BoundQueryExecuted["BoundQueryExecuted<br/>(Event)"]:::event
        CMD_ExecuteBoundQuery --> AGG_BoundQueryExecution --> EVT_BoundQueryExecuted
    end

    subgraph execute_script_ov["execute-script (exasol_script)"]
        direction TB
        CMD_ExecuteScript["ExecuteScript<br/>(Command)"]:::command
        AGG_ScriptExecution["SCRIPT_EXECUTION<br/>(Aggregate)"]:::aggregate
        EVT_ScriptExecuted["ScriptExecuted /<br/>ScriptExecutionFailed<br/>(Event)"]:::event
        CMD_ExecuteScript --> AGG_ScriptExecution --> EVT_ScriptExecuted
    end

    subgraph reconcile_schema_ov["reconcile-schema (exasol_schema)"]
        direction TB
        CMD_CreateSchema["CreateSchema<br/>(Command)"]:::command
        AGG_Schema["SCHEMA<br/>(Aggregate)"]:::aggregate
        EVT_SchemaCreated["SchemaCreated<br/>(Event)"]:::event
        CMD_CreateSchema --> AGG_Schema --> EVT_SchemaCreated
    end

    subgraph reconcile_user_ov["reconcile-user (exasol_user)"]
        direction TB
        CMD_CreateUser["CreateUser<br/>(Command)"]:::command
        AGG_User["EXASOL_USER<br/>(Aggregate)"]:::aggregate
        EVT_UserCreated["UserCreated<br/>(Event)"]:::event
        CMD_CreateUser --> AGG_User --> EVT_UserCreated
    end

    subgraph reconcile_role_ov["reconcile-role (exasol_role)"]
        direction TB
        CMD_CreateRole["CreateRole<br/>(Command)"]:::command
        AGG_Role["EXASOL_ROLE<br/>(Aggregate)"]:::aggregate
        EVT_RoleCreated["RoleCreated<br/>(Event)"]:::event
        CMD_CreateRole --> AGG_Role --> EVT_RoleCreated
    end

    subgraph reconcile_grants_ov["reconcile-grants (exasol_grants)"]
        direction TB
        CMD_GrantPrivilege["GrantPrivilege<br/>(Command)"]:::command
        AGG_Grant["GRANT<br/>(Aggregate)"]:::aggregate
        EVT_PrivilegeGranted["PrivilegeGranted<br/>(Event)"]:::event
        CMD_GrantPrivilege --> AGG_Grant --> EVT_PrivilegeGranted
    end

    operator --> CMD_GatherInfo
    operator --> CMD_ExecuteQueryBatch
    operator --> CMD_ExecuteBoundQuery
    operator --> CMD_ExecuteScript
    operator --> CMD_CreateSchema
    operator --> CMD_CreateUser
    operator --> CMD_CreateRole
    operator --> CMD_GrantPrivilege

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef readmodel fill:#D3D3D3,stroke:#333,color:#000
    

GRANT records reference EXASOL_USER or EXASOL_ROLE as the granted principal, and a schema or table as the granted object.

Color

Meaning

Light blue

Command: what the Ansible Operator asked for

Khaki

Aggregate: the consistency boundary that applies the command

Orange

Event: the fact recorded once the command is applied

Light gray

Read model: a query result with no state change

Tomato (per-use-case diagrams below)

Failure: a rejected or failed command

gather-server-info (exasol_info)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["SERVER_INFO<br/>(Aggregate, read-only)"]:::aggregate
    CMD_Gather["GatherServerInfo<br/>(Command)"]:::command
    RM_Reported["ServerInfoReported<br/>(Read Model)<br/>version, database_name,<br/>cluster_size; always changed=false"]:::readmodel

    operator --> CMD_Gather --> AGG --> RM_Reported

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef readmodel fill:#D3D3D3,stroke:#333,color:#000
    

This use case never mutates Exasol state, so it has no domain event: only a read model is produced.

Source scenario: exasol_info.feature.

execute-query-batch (exasol_query)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["QUERY_EXECUTION<br/>(Aggregate)"]:::aggregate

    CMD_Execute["ExecuteQueryBatch<br/>(Command)<br/>one statement or an<br/>ordered statement batch;<br/>no bound args"]:::command
    EVT_Executed["QueryBatchExecuted<br/>(Event)<br/>changed=true for any<br/>non-read-only statement"]:::event

    CMD_Predict["PredictQueryBatchExecution<br/>(Command, check mode)"]:::command
    EVT_Predicted["QueryBatchExecutionPredicted<br/>(Event)<br/>read-only statements still run;<br/>any write statement is skipped<br/>and the whole batch is predicted"]:::event

    CMD_BadAuth["ExecuteQuery<br/>(Command, bad credentials)"]:::command
    EVT_Failed["QueryBatchExecutionFailed<br/>(Event, Failure)<br/>sanitized authentication error,<br/>no secret values exposed"]:::failure

    operator --> CMD_Execute
    operator --> CMD_Predict
    operator --> CMD_BadAuth

    CMD_Execute --> AGG --> EVT_Executed
    CMD_Predict --> AGG --> EVT_Predicted
    CMD_BadAuth --> AGG --> EVT_Failed

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef failure fill:#FF6347,stroke:#333,color:#000
    

ExecuteQueryBatch accepts either one unbound statement or a list of statements. A list runs on one connection in supplied order. positional_args and named_args are not accepted for this use case because the module does not infer which statement should receive them.

Source scenarios: exasol_query.feature, exasol_query.feature (Execute statement batch on one connection and Skip mixed read-write batch in check mode).

execute-bound-query (exasol_query)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["QUERY_EXECUTION<br/>(Aggregate)"]:::aggregate

    CMD_Execute["ExecuteBoundQuery<br/>(Command)<br/>exactly one statement with<br/>positional and/or named args"]:::command
    EVT_Executed["BoundQueryExecuted<br/>(Event)<br/>changed=true for a<br/>non-read-only statement"]:::event

    CMD_Invalid["ExecuteBoundQuery<br/>(Command, invalid args)"]:::command
    EVT_Rejected["BoundQueryRejected<br/>(Event, Failure)<br/>a statement batch is supplied,<br/>or placeholders and arguments<br/>do not match"]:::failure

    operator --> CMD_Execute
    operator --> CMD_Invalid

    CMD_Execute --> AGG --> EVT_Executed
    CMD_Invalid --> AGG --> EVT_Rejected

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef failure fill:#FF6347,stroke:#333,color:#000
    

ExecuteBoundQuery accepts one query statement only. It uses positional_args for ? placeholders and named_args for :name placeholders. Supplying either argument collection with a statement batch is rejected before execution.

Source scenarios: exasol_query.feature (Bind positional arguments, Bind named arguments, and Reject bound arguments for statement batch).

execute-script (exasol_script)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["SCRIPT_EXECUTION<br/>(Aggregate)"]:::aggregate

    CMD_Execute["ExecuteScript<br/>(Command)<br/>a semicolon-separated<br/>multi-statement script;<br/>CREATE ... SCRIPT bodies end<br/>with a standalone '/' line"]:::command
    EVT_Executed["ScriptExecuted<br/>(Event)<br/>changed=true for any<br/>non-read-only statement<br/>pyexasol split from the script"]:::event

    CMD_Predict["PredictScriptExecution<br/>(Command, check mode)"]:::command
    EVT_Predicted["ScriptExecutionPredicted<br/>(Event)<br/>read-only scripts still run;<br/>any other script is predicted<br/>as one opaque unit, not the<br/>real per-statement split"]:::event

    CMD_Failing["ExecuteScript<br/>(Command, failing statement)"]:::command
    EVT_Failed["ScriptExecutionFailed<br/>(Event, Failure)<br/>stops at the first failing<br/>statement; earlier statements'<br/>effects are not undone;<br/>sanitized error message"]:::failure

    operator --> CMD_Execute
    operator --> CMD_Predict
    operator --> CMD_Failing

    CMD_Execute --> AGG --> EVT_Executed
    CMD_Predict --> AGG --> EVT_Predicted
    CMD_Failing --> AGG --> EVT_Failed

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef failure fill:#FF6347,stroke:#333,color:#000
    

ExecuteScript never accepts positional_args or named_args: Ansible’s own argument-spec validation rejects them before the command reaches this aggregate, since pyexasol does not support bound parameters for scripts.

Source scenarios: exasol_script.feature, exasol_script.feature.

reconcile-schema (exasol_schema)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["SCHEMA<br/>(Aggregate)"]:::aggregate

    CMD_Create["CreateSchema<br/>(Command)"]:::command
    EVT_Created["SchemaCreated<br/>(Event)"]:::event

    CMD_Drop["DropSchema<br/>(Command)"]:::command
    EVT_Dropped["SchemaDropped<br/>(Event)"]:::event

    CMD_Owner["ChangeSchemaOwner<br/>(Command)"]:::command
    EVT_Owner["SchemaOwnerChanged<br/>(Event)"]:::event

    CMD_Comment["SetSchemaComment<br/>(Command)"]:::command
    EVT_Comment["SchemaCommented<br/>(Event)"]:::event

    CMD_Rename["RenameSchema<br/>(Command)"]:::command
    EVT_Renamed["SchemaRenamed<br/>(Event)"]:::event

    CMD_Quota["SetSchemaRawSizeLimit<br/>(Command)"]:::command
    EVT_Quota["SchemaQuotaChanged<br/>(Event)"]:::event

    CMD_Predict["PredictSchemaChange<br/>(Command, check mode)<br/>covers every branch above"]:::command
    EVT_Predicted["SchemaChangePredicted<br/>(Event)<br/>same plan, no statement executed"]:::event

    CMD_DropUnsafe["DropSchema<br/>(Command, non-empty, no cascade)"]:::command
    EVT_DropRejected["SchemaDropRejected<br/>(Event, Failure)<br/>CASCADE required"]:::failure

    CMD_OwnerMissing["ChangeSchemaOwner<br/>(Command, owner missing)"]:::command
    EVT_OwnerFailed["SchemaOwnerAssignmentFailed<br/>(Event, Failure)<br/>CREATE SCHEMA already<br/>committed before the failure"]:::failure

    operator --> CMD_Create
    operator --> CMD_Drop
    operator --> CMD_Owner
    operator --> CMD_Comment
    operator --> CMD_Rename
    operator --> CMD_Quota
    operator --> CMD_Predict
    operator --> CMD_DropUnsafe
    operator --> CMD_OwnerMissing

    CMD_Create --> AGG --> EVT_Created
    CMD_Drop --> AGG --> EVT_Dropped
    CMD_Owner --> AGG --> EVT_Owner
    CMD_Comment --> AGG --> EVT_Comment
    CMD_Rename --> AGG --> EVT_Renamed
    CMD_Quota --> AGG --> EVT_Quota
    CMD_Predict --> AGG --> EVT_Predicted
    CMD_DropUnsafe --> AGG --> EVT_DropRejected
    CMD_OwnerMissing --> AGG --> EVT_OwnerFailed

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef failure fill:#FF6347,stroke:#333,color:#000
    

When the observed schema state already matches the requested state (existence, owner, comment, rename, quota), the runtime issues no command and reports changed=false: an implicit “leave unchanged” branch behind every command above.

Source scenario: exasol_schema.feature.

reconcile-user (exasol_user)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["EXASOL_USER<br/>(Aggregate)"]:::aggregate

    CMD_Create["CreateUser<br/>(Command, authentication_method=password)<br/>CREATE USER ... IDENTIFIED BY, plus<br/>GRANT CREATE SESSION when create_session=true"]:::command
    CMD_CreateLdap["CreateUser<br/>(Command, authentication_method=ldap)<br/>CREATE USER ... IDENTIFIED AT LDAP AS, plus<br/>GRANT CREATE SESSION when create_session=true"]:::command
    EVT_Created["UserCreated<br/>(Event)"]:::event

    CMD_UpdatePwd["UpdateUserPassword<br/>(Command, update_password=always)"]:::command
    EVT_PwdUpdated["UserPasswordUpdated<br/>(Event)"]:::event

    CMD_UpdateLdap["UpdateUserLdapDn<br/>(Command, authentication_method=ldap,<br/>ldap_dn differs from DISTINGUISHED_NAME)"]:::command
    EVT_LdapUpdated["UserLdapDnUpdated<br/>(Event)"]:::event

    CMD_Drop["DropUser<br/>(Command, cascade)"]:::command
    EVT_Dropped["UserDropped<br/>(Event)"]:::event

    CMD_Predict["PredictUserChange<br/>(Command, check mode)<br/>covers every branch above"]:::command
    EVT_Predicted["UserChangePredicted<br/>(Event)<br/>same plan, no statement executed"]:::event

    operator --> CMD_Create
    operator --> CMD_CreateLdap
    operator --> CMD_UpdatePwd
    operator --> CMD_UpdateLdap
    operator --> CMD_Drop
    operator --> CMD_Predict

    CMD_Create --> AGG
    CMD_CreateLdap --> AGG
    AGG --> EVT_Created
    CMD_UpdatePwd --> AGG --> EVT_PwdUpdated
    CMD_UpdateLdap --> AGG --> EVT_LdapUpdated
    CMD_Drop --> AGG --> EVT_Dropped
    CMD_Predict --> AGG --> EVT_Predicted

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    

update_password=on_create only sets a password while creating the user. For an existing password-authenticated user, the runtime issues no password-update command and reports changed=false; Exasol does not expose password equality for comparison. update_password does not apply to LDAP-authenticated users.

authentication_method defaults to ldap when ldap_dn is supplied, otherwise password. create_session (default true) makes the CREATE SESSION grant on user creation optional rather than implicit.

Source scenarios: exasol_user.feature, exasol_user.feature.

reconcile-role (exasol_role)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["EXASOL_ROLE<br/>(Aggregate)"]:::aggregate

    CMD_Create["CreateRole<br/>(Command)"]:::command
    EVT_Created["RoleCreated<br/>(Event)"]:::event

    CMD_Drop["DropRole<br/>(Command, cascade)"]:::command
    EVT_Dropped["RoleDropped<br/>(Event)"]:::event

    CMD_Predict["PredictRoleChange<br/>(Command, check mode)<br/>covers every branch above"]:::command
    EVT_Predicted["RoleChangePredicted<br/>(Event)<br/>same plan, no statement executed"]:::event

    operator --> CMD_Create
    operator --> CMD_Drop
    operator --> CMD_Predict

    CMD_Create --> AGG --> EVT_Created
    CMD_Drop --> AGG --> EVT_Dropped
    CMD_Predict --> AGG --> EVT_Predicted

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    

When the role already exists (for CreateRole) or is already absent (for DropRole), the runtime issues no command and reports changed=false.

Source scenario: exasol_role.feature.

reconcile-grants (exasol_grants)#

        flowchart LR
    operator(["Ansible Operator"])
    AGG["GRANT<br/>(Aggregate)"]:::aggregate

    CMD_Grant["GrantPrivilege<br/>(Command)<br/>system_privileges or<br/>object_privileges, state=present"]:::command
    EVT_Granted["PrivilegeGranted<br/>(Event)"]:::event

    CMD_Revoke["RevokePrivilege<br/>(Command, state=absent)"]:::command
    EVT_Revoked["PrivilegeRevoked<br/>(Event)"]:::event

    CMD_Predict["PredictGrantChange<br/>(Command, check mode)<br/>covers both branches above"]:::command
    EVT_Predicted["GrantChangePredicted<br/>(Event)<br/>same plan, no statement executed"]:::event

    CMD_Ambiguous["GrantPrivilege<br/>(Command, user and role<br/>both supplied)"]:::command
    EVT_Rejected["GrantRequestRejected<br/>(Event, Failure)<br/>principal must be exactly<br/>one of user or role"]:::failure

    operator --> CMD_Grant
    operator --> CMD_Revoke
    operator --> CMD_Predict
    operator --> CMD_Ambiguous

    CMD_Grant --> AGG --> EVT_Granted
    CMD_Revoke --> AGG --> EVT_Revoked
    CMD_Predict --> AGG --> EVT_Predicted
    CMD_Ambiguous --> AGG --> EVT_Rejected

    classDef command fill:#ADD8E6,stroke:#333,color:#000
    classDef aggregate fill:#F0E68C,stroke:#333,color:#000
    classDef event fill:#FFA500,stroke:#333,color:#000
    classDef failure fill:#FF6347,stroke:#333,color:#000
    

GRANT is keyed by (principal, principal_type, object, privilege). When the observed privilege state already matches the requested state, the runtime issues no command and reports changed=false.

A single GrantPrivilege/RevokePrivilege command reconciles a batch of such tuples at once: one per system_privileges entry, and one per privilege in each object_privileges[] entry. Each object_privileges[] entry names its own schema, an optional object, and an optional object_type (function, script, table, view, or virtual_schema) that disambiguates same-named objects.

Source scenarios: exasol_grants.feature.