Documentation of Cherry Framework

Perform Business Missions

'You say who I am and what I do. I'm at your services', said the Agent...

Here are some Java examples on how to provide Tools to the Agent, assign Missions to him, store data in Agent's Memory and retrieve them in subsequent Missions.

Initialize Agent

You can initialize a generic Agent as follows:

 import static io.magentys.AgentProvider.agent;

    Agent fxTrader = agent();
    fxTrader.obtains(new BloombergServiceClient(), new ChromeDriver());
    fxTrader.keepsInMind("username","traderAwesome");
    fxTrader.keepsInMind("password","yadda");
    fxTrader.keepsInMind("dob", LocalDate.of(1982, Month.MARCH, 11));

Or, even better you can also create a custom Agent by extending Agent:

  import io.magentys.Agent;
  import io.magentys.CoreMemory;
  import java.time.LocalDate;
  import java.time.Month;

  public class FxTrader extends Agent {

      public FxTrader(){
          super(new CoreMemory());
          this.obtains(
            new BloombergServiceClient(),
            new ChromeDriver()
          );
          this.keepsInMind("username","traderAwesome")
          this.keepsInMind("password","yadda")
          this.keepsInMind("dob", LocalDate.of(1982, Month.MARCH, 11));
       }
   }

Similarly, let's create a 1st line support person who has access to our production databases and servers:

     public class SupportPerson extends Agent {

         public SupportPerson(final DatabaseDriver dbDriver, final SshClient sshClient){
               super(new CoreMemory());
               this.obtains(
                            dbDriver,
                            sshClient
               );
               this.keepsInMind("username","admin");
               this.keepsInMind("password","***********");
          }
      }

As you can see you will have to provide an instance of a Memory-type object (in our case we use CoreMemory); this is fundamental to Agent's operation. Then, you can also (as above) prime your customised Agent with some setup data (e.g. his credentials or even other objects which your agent will find useful later on when accomplishing missions).

Create and Perform Missions using Tools and Memory

Bear with us as we are going to first show you the end result and followingly on how it works under the hood.Ready? Let's do this!

Ok, lets suppose we have a set of Missions using the tools assign to FxTrader and SupportPerson, namely:

  1. Authentication to Bloomberg (separate as we might want to authenticate for verious purposes).
  2. Fetching of Rate from Bloomberg
  3. Authenticatation to your FX Platform software
  4. Verification that displayed value in our FX Platform is equal to the Bloomberg one
  5. Verification that rate value is successfully stored in Database (that's one for the Support Person).

Let's instantiate our agents and start assigning missions to them:

  FxTrader danielTheTrader = new FxTrader();
  SystemSupport johnTheSupportPerson = new SystemSupport(dbDriver,sshClient);

  final String currencyPair = "GBPUSD"; // that can come from Cucumber step as well...

Here's the usage of the Missions:

  BigDecimal rateInBloomberg = danielTheTrader
                          .performs(authenticationInBloomberg())
                           .andHe(obtainsBloombergRateFor(currencyPair));

  BigDecimal rateInFXPlatformSite = danielTheTrader.performs(authenticationInFXPlatformSite())
                                 .andHe(obtainsDisplayedRateFor(currencyPair));

  verifyAs(danielTheTrader).that(rateInFXPlatformSite, is(equalTo(rateInBloomberg));                             

Can we refactor our Missions to store their results to Agent's memory and test the same scenario with JUnit Assertions? Yes, and the result would look like this:

   danielTheTrader.performsAll(
                            authenticatesInBloomberg(),
                            obtainsBloombergRateFor(currencyPair),
                            authenticatesInFXPlatformSite(),
                            obtainsDisplayedRateFor(currencyPair)
                            );
   BigDecimal rateInBloomberg = danielTheTrader.recalls("bloomberg.rate", BigDecimal.class);
   BigDecimal rateInFXPlatformSite = danielTheTrader.recalls("our.rate", BigDecimal.class);

  assertThat(rateInFXPlatformSite, is(equalTo(rateInBloomberg));

Time to ask our system support person to verify that this is the value we have in DB:

   verifyAs(johnTheSupportPerson).that(latestRateFromDBFor(currencyPair),isEqualTo(rateInBloomberg));

Ok, but how did we do it?

Mission is nothing but a generic interface which looks like this:

     public interface Mission<RESULT> {
          RESULT accomplishAs(Agent agent);
     }

Therefore when you call agent.perform(mission) the agent passes himself as a visitor to the executed mission; thus we have applied Uncle Bob's beloved Visitor Pattern.

Every Mission in our example above, requires one of the tools from the agent (in other cases it can be more than one but just to keep it simple). So if we take a better look at the obtainsDisplayedRateFor Daniel the FX Trader had to accomplish, then we will see how the tools are used and how these Missions look like internally.

With regards to obtainsDisplayedRateFor we can imagine an implementation of the Mission interface as follows:

  import io.magentys.Agent;
  import io.magentys.Mission;
  import java.math.BigDecimal;

  // BigDecimal is the return type of your mission
  public class ObtainDisplayedRate implements Mission<BigDecimal> {

      final String ccyPair;
      public ObtainDisplayedRate(final String ccyPair){
          this.ccyPair = ccyPair;
      }

      // Static constructor:

      public static ObtainDisplayedRate obtainsDisplayedRateFor(final String ccyPair){
          return new ObtainDisplayedRate(ccyPair);
      }

      // this is the only method a mission has to implement
      @Override
      public BigDecimal accomplishAs(Agent fxTrader) {

          // get the BloombergServiceClient which was assigned to the agent (fxTrader)
          // in the constructor body
          BloombergServiceClient bsc = agent.usingThe(BloombergServiceClient.class);

          // use the BloombergServiceClient here...
          BigDecimal result = bsc.getResult();

          //also store in memory
          fxTrader.keepsInMind("bloomberg.rate", result);
          return result;
      }
  }

So the concept is simple. Use Missions as functions your agent has to perform using the tools you've assigned to him, (optionally) store results (final or interim) into his memory, and return the results of your mission but to the caller.

Can I chain Missions together?

Of course! The only thing you have to do is extend Mission<Agent> and return the agent as follows:

  public class ActUponSystemWithoutResult implements Mission<Agent> {
      @Override
      public Agent accomplishAs(Agent agent) {

          // perform mission here...
               /*
                ... more code goes here
               */
          // and then:
          return agent;
      }
  }

the result will look like this:

    agent.performs(loginToTheSystem()).and(performsSomethingElse()).and(...