Proposed High Level Outline for Deprecating the ActorStack - samvera/hyrax GitHub Wiki

The goal of this refactor is to remove the case logic around the create_work and update_work methods in the Hyrax::WorksControllerBehavior module. Aspirationally, we would factor away the actor stack in favor of change sets (form) and transactions.

Below is an example of the two logic paths, one for ActiveFedora::Base objects and one Valkyrie` objects.

# See ./app/controllers/concerns/hyrax/works_controller_behavior.rb
def create_work
  case curation_concern
  when ActiveFedora::Base
    # I copied the following two lines to provide more context for the example
    actor_environment = Actors::Environment.new(curation_concern, current_ability, attributes_for_actor)
    actor = Hyrax::CurationConcern.actor

    actor.create(actor_environment)
  else
    # I copied the following two lines to provide more context for the example
    transactions = Hyrax::Transactions::Container
    form = work_form_service.build(curation_concern, current_ability, self)

    @curation_concern = form.validate(params[hash_key_for_curation_concern]) &&
                        transactions['change_set.create_work'].call(form).value!
  end
end

For ActiveFedora::Base we build the actor_environment, analogue to Rack middleware’s env. With that environment we tell the actor to perform a create. The actor is responsible for both data validation and performing a variety of side-effect producing processes to manipulate the state of the form attributes and the model.

Multiple actors compose the stack (see ./app/services/hyrax/default_middleware_stack.rb). Each actor implements a create and update method that receives the actor_environment.

The pattern of each actor is roughly as follows:

  1. Do something before calling the next actor in the stack a. Validation? b. Alter params? c. Make data changes? d. Launch a job? e. It all depends on the implementation
  2. Call the next step in the actor stack
  3. Do something after calling the next actor in the stack a. As step one, it could be just about anything

With multiple actors in the stack, as we work our way down the stack, we stop when the actor returns from the called method; It could stop because of an error or because validation failed or some other reason.

It should be noted that many "states" might change in the process. The actor stack as implemented, mutates parameters, the given curation_concern, and the persistence and indexing layers. There is minimal transactionality surrounding this behavior, so we can easily end up with partially applied processes.

This loose pattern allows for the flexibility of before and after behavior, but creates an opaque process. To know what's happening, you need to follow the stack, and look at what happens both before and after calling the next actor.

For the other case (e.g. not ActiveFedora::Base), we separate the validations (e.g. form.validate)from the state changing operations (e.g. transactions[‘change_set.create_work’].call(form)).

Aspirationally, we want the form.validate to perform the same-ish validations as the actor stack steps. Likewise, when we use the named transaction (e.g. transactions[‘change_set.create_work’]) we want to perform the same-ish state changing processes as the actor stack steps.

From ./app/services/hyrax/default_middleware_stack.rb we have the following actors:

  • Hyrax::Actors::OptimisticLockValidator
  • Hyrax::Actors::CreateWithRemoteFilesActor
  • Hyrax::Actors::CreateWithFilesActor
  • Hyrax::Actors::CollectionsMembershipActor
  • Hyrax::Actors::AddToWorkActor
  • Hyrax::Actors::AttachMembersActor
  • Hyrax::Actors::ApplyOrderActor
  • Hyrax::Actors::DefaultAdminSetActor
  • Hyrax::Actors::InterpretVisibilityActor
  • Hyrax::Actors::TransferRequestActor
  • Hyrax::Actors::ApplyPermissionTemplateActor
  • Hyrax::Actors::CleanupFileSetsActor
  • Hyrax::Actors::CleanupTrophiesActor
  • Hyrax::Actors::FeaturedWorkActor
  • Hyrax::Actors::ModelActor
  • Hyrax::Actors::InitializeWorkflowActor

The above actors in the actor stack need to be closely read, extracting logic for validation (e.g. form.validate) and applying changes to the data (e.g. transaction). Going forward, I believe there are two primary epic issues that need crafting:

  1. Extract ActorStack validations
  2. Extract ActorStack transactions

As we don't know how downstream adopters modify the stack, care must be taken to provide a clear forward path.