Intro To Actions - tsgrp/HPI GitHub Wiki

#Action Basics The simple definition of an OC/HPI action is

Some unit of work that takes place in the repository- against none, one, or many nodes - that may or may not cause a change and return a result. A users ability to execute an action is based on a set of configurable conditions related to the the runtime state of the user, object and other context information.

Simple right? Some examples:

  • Returning information about an object (wizard show audits)
  • Creating an object(upload)
  • Modifying an object (properties update/checkin)
  • Making one or more changes in a specific order representing a business process. (Wizard approval)

This is of course by no means an extensive list of examples, just a few to get you warmed up. The goal of the following documentation is to provide an understanding of the framework and some guidelines on how and when to use actions.

The underlying pattern of the action framework consists of the following vocabulary:

  • ActionDefinition - A description of an action that can take place and what parameters need to be present
  • ActionExecuter - Classes that perform actions will implement this interface and override a execute method. Inside the execute method will be the logic to - using the parameters supplied - perform a specific action
  • ConditionEvaluator - Similar to an ActionExecuter, a ConditionEvaluator can perform evaluation of a certain type of condition.
  • Action - A specific instance of an action we would like to happen and the parameters that are relevant to this specific execution of the action.
  • Condition - A specific instance of an condition we would like to happen and the parameters that effect this particular evaluation

The challenging aspect to understanding the action framework is to not think of an action as a method. An action is a noun not a verb. The checkin action isn't the same as the checkin method. The checkin actionexecuter can call the checkin method, but the check-in action is a runtime instance of the request to/and parameters for a checkin to take place.

Analogy time:

Let's take the action COOK. If you want to eat make food you will have to take the action of Cooking it. Now as I said before this is not to be confused with the oven's method cook() or the stove's method cook().

Pretend you want a 3 course steak dinner. You could go to the kitchen yourself and use:

saladBar.makeSalad(cesear, dressingOnTheSide)
grill.cook(steak, mediumRare)
oven.bake(chocolateCake)

That works fine if you know how to operate everything and don't mind doing it, but what if some of your friend wants food too. Now it looks like this:

saladBar.makeSalad(cesear, dressingOnTheSide)
grill.cook(steak, mediumRare)
oven.bake(chocolateCake)
saladBar.makeSalad(house)
grill.cook(tuna, mediumWell)
oven.bake(sugarCookie)

This still isn't awful, now let's say it's not just your friends. Your food is so good, you're going to open a restaurant. Well it's get pretty tedious tracking all those variations individually. So you hire some help.

  • A pastry chef to bake
  • A linecook to grill
  • A salad-master to assemble salads.

These are your action executer's you simple give them an order with criteria and they perform the 'cook' action for you.

Let's define some action definitions:

  • The CookSaladAction needs to know a type and where to put the dressing
  • The CookMeat action needs to know the type of meat and the doneness
  • The CookDessert action needs to know the type of desert.

Now you want your users to have a nice experience. So you put in a good looking waiter(frontend)

He/She can create orders(actions) and give them to your chefs (actionexecutors). Now your servers only need to know what types of salads/meats/desserts to offer and what options each of those items has. They don't actually have to know how to operate the grill or oven, or how to do knife.slice(tomato).

Let's take it a step further...what if a guest is under 21. Then the waiter knows that he cannot offer the PourBooze option if the idCheckEvaluater fails. The waiter has to create an instance of the condition, sets the required birthDay parameter on the condition and runs it through an evaluator idOK which says yes or no to offering the PourBooze Action. This works out create because the idOkEvaluator can have different implementation in the US (21+) or other countries (18+).

Ok, well that was starting to fall apart, but you get the idea. Let's talk spring. You can think of the spring config as the definition of your environment. In the example above you would see four actions:

  • CookSalad - points to the sousChef
  • CookMeat - points to the lineCook
  • CookDessert - points to the pastary chef
  • PourBooze - points to the bartender
    • Condition id="OldEnoughUSA" points to the idOKEvaluator with injected value of "21"

Now HPI can create an action. The basic idea is roughly:

Create a group of actions you'd like to offer the user. (Create four new actions set the name of each one to the id of the action in spring) Call actions.evaluate() this will return an array of actions where each action has a list of conditions and whether they passed or not. Next just show the users the actions that passed their conditions. When an user clicks one, show a modal that collects additional information needed to perform the action. (You can evaluate whether the user can order booze without knowing what they want, but have to know what they want to actually PourBooze)

Now what's interesting is what if your LinecookActionExecuter can cook meat or vegetables, but you want one interface that shows a selection of meats for the cook meat action and a list of veggies for the cookVeggies. Easy create two entries in your spring config:

id="cookMeats" class="LineCookActionExecuter"
id="cookVeggies" class="LineCookActionExecuter"

Now in HPI you configure both actions on your action menu, setup the action interfaces accordingly and the front end will use different views, but the underlying action can be executed in the same place. This is particularly useful if you have this:

  • id="cookMeats" class="LineCookActionExecuter"
    • condition: id="eatsMeat"
  • id="cookVeggies" class="LineCookActionExecuter"
    • conditions: id="eatsMeat" inverse="true"

Now only one action or the other will show up for each user.

This also demonstrates how a single conditionevaluater can be shared among serveral actions.