defmodule AshCommanded.Commanded.Dsl do @moduledoc """ The DSL extension for using Commanded with Ash resources. This extension provides the ability to define commands, events, projections, and event handlers using Ash resources, and integrates with Commanded to provide CQRS and Event Sourcing patterns. ## Usage ```elixir defmodule MyApp.User do use Ash.Resource, extensions: [AshCommanded.Commanded.Dsl] commanded do commands do command :register_user do fields([:id, :email, :name]) identity_field(:id) end end events do event :user_registered do fields([:id, :email, :name]) end end projections do projection :user_registered do changes(%{status: :active}) end end event_handlers do handler :user_registered_notification do events [:user_registered] action fn event, _metadata -> MyApp.Notifications.send_welcome_email(event.email) :ok end end end end end ``` """ require Logger alias AshCommanded.Commanded.Sections.CommandsSection @commands_section %Spark.Dsl.Section{ name: :commands, describe: "Define commands that trigger state changes in the resource", schema: CommandsSection.schema(), entities: CommandsSection.entities(), imports: [] } alias AshCommanded.Commanded.Sections.EventsSection @events_section %Spark.Dsl.Section{ name: :events, describe: "Define events that are emitted by commands", schema: EventsSection.schema(), entities: EventsSection.entities(), imports: [] } alias AshCommanded.Commanded.Sections.ProjectionsSection @projections_section %Spark.Dsl.Section{ name: :projections, describe: "Define how events affect the resource state", schema: ProjectionsSection.schema(), entities: ProjectionsSection.entities(), imports: [] } alias AshCommanded.Commanded.Sections.EventHandlersSection @event_handlers_section %Spark.Dsl.Section{ name: :event_handlers, describe: "Define general purpose handlers that respond to events", schema: EventHandlersSection.schema(), entities: EventHandlersSection.entities(), imports: [] } alias AshCommanded.Commanded.Sections.ApplicationSection @application_section %Spark.Dsl.Section{ name: :application, describe: "Configure the Commanded application", schema: ApplicationSection.schema(), entities: ApplicationSection.entities(), imports: [] } # Top-level section that contains all other sections @commanded_section %Spark.Dsl.Section{ name: :commanded, describe: "Configure CQRS and Event Sourcing with Commanded", sections: [ @commands_section, @events_section, @projections_section, @event_handlers_section, @application_section ] } # The main extension use Spark.Dsl.Extension, sections: [@commanded_section], transformers: [ # Collect options from DSL AshCommanded.Commanded.Transformers.CollectParameterTransforms, AshCommanded.Commanded.Transformers.CollectParameterValidations, AshCommanded.Commanded.Transformers.CollectTransactionOptions, # Generate modules AshCommanded.Commanded.Transformers.GenerateCommandModules, AshCommanded.Commanded.Transformers.GenerateEventModules, AshCommanded.Commanded.Transformers.GenerateProjectionModules, AshCommanded.Commanded.Transformers.GenerateProjectorModules, AshCommanded.Commanded.Transformers.GenerateEventHandlerModules, AshCommanded.Commanded.Transformers.GenerateAggregateModule, AshCommanded.Commanded.Transformers.GenerateDomainRouterModule, AshCommanded.Commanded.Transformers.GenerateMainRouterModule, AshCommanded.Commanded.Transformers.GenerateCommandedApplication ], verifiers: [ AshCommanded.Commanded.Verifiers.ValidateCommandFields, AshCommanded.Commanded.Verifiers.ValidateCommandNames, AshCommanded.Commanded.Verifiers.ValidateEventFields, AshCommanded.Commanded.Verifiers.ValidateEventNames, AshCommanded.Commanded.Verifiers.ValidateProjectionEvents, AshCommanded.Commanded.Verifiers.ValidateProjectionActions, AshCommanded.Commanded.Verifiers.ValidateProjectionChanges, AshCommanded.Commanded.Verifiers.ValidateEventHandlerEvents, AshCommanded.Commanded.Verifiers.ValidateEventHandlerActions ] @doc """ Determine if a resource uses the `commanded` extension. ## Examples iex> AshCommanded.Commanded.Dsl.extension?(SomeResource) true iex> AshCommanded.Commanded.Dsl.extension?(OtherResource) false """ @spec extension?(Ash.Resource.t()) :: boolean() def extension?(resource) do extensions = Spark.extensions(resource) __MODULE__ in extensions end @doc """ Return the section definitions for use in custom DSL extensions. This is used in tests to create custom DSL extensions without verifiers. """ @spec __sections__() :: Spark.Dsl.Section.t() def __sections__() do @commanded_section end @doc """ Get the application configuration from the DSL. ## Parameters * `dsl` - The DSL state. ## Returns A keyword list of application configuration options, or nil if no application configuration is defined. ## Examples iex> application(dsl) [otp_app: :my_app, event_store: Commanded.EventStore.Adapters.InMemory] iex> application(dsl_without_app_config) nil """ @spec application(Spark.Dsl.t()) :: keyword() | nil def application(dsl) do opts = Spark.Dsl.Extension.get_opt(dsl, [:commanded, :application], nil, nil) case opts do nil -> nil opts -> opts end end end