Class BevGraphBuilder

java.lang.Object
com.tccc.kos.ext.dispense.pipeline.beverage.graph.BevGraphBuilder

public class BevGraphBuilder extends Object
Builder used to construct a BevGraph which captures the dependencies between pumps, ingredients, beverages, brands and groups. By describing these dependencies, PourEngine can automatically compute availability and visibility information for top level objects like beverages, brands and groups without any developer effort aside from constructing the graph. Once described, the graph can also be used to extract the proper way to pour a beverage which automatically factors in pump availability, assigned ingredients, as well as multiple ways to pour a beverage due to multiple instances of an ingredient being available.

Generally speaking, the dependency graph is constructed from layers of connected nodes, where each node represents an operation applied to one or more nodes in the layer below. For example, consider water as a beverage. To pour water only involves a single pump but this can be represented as the following dependency:

     pump(water) -> beverage(water)
 
That is, there is a pump with water assigned and a beverage named water which requires a pump that can pour water. This is a two layer graph where the beverage depends on the pump. If we add another ingredient we get the following:
     pump(water) -> beverage(Coke)
     pump(Coke)  ->
 
In this example the beverage Coke depends on two ingredients, water and Coke. If either ingredient is unavailable then the beveage is unavailable.

In the diagrams above, the pumps and beverages would be represented as nodes and the connections between them are parent / child relationships. Each node can define how it computes availability information based on its children and in the example above a beverage would use a logical AND relationship. The beverage is only available if all children are available.

Extending this example, consider the case where Coke is available on more than one pump. This can be handled by introducing a new layer in the graph for ingredients:

     pump(water) -> ingredient(water) -> beverage(Coke)
     pump(Coke)  -> ingredient(Coke)  ->
     pump(Coke)  ->
 
In this example, the beverage depends on ingredients, not pumps. The ingredients then depend on the pumps that provide the ingredients. While the beverage node uses a logical AND relationship, the ingredient nodes would use a logical OR relationship as Coke ingredient is available if any pump is available to pump it. By describing these five nodes, the PourEngine will be able to compute the availability of the beverage Coke without any other developer input.

The builder also has support for beverages, brands and groups. By designating a node as a beverage, it will be shared with external clients (ui code) via endpoints and websocket events. These three top level features are defined as follows:

  • beverages : These are nodes designated as beverages. These will be visible via endpoints and any changes in the state of the graph that impacts a beverage node will be sent as an update via websocket events.
  • brands : A brand tends to be a collection of related beverages. For example, a brand called Coke may contain beverages such as Coke, Cherry Coke, Coke with Lime, and so on. A brand node will reflect availability using a logical OR on the child beverages so the brand will be available if any beverage is available. Brands exist purely for convenience and need not be used if the application does not require them.
  • groups : Functionally identical to brands but grouped into a different part of the endpoint payloads for convenience. A ui may use brands to drive the main home screen but use groups for an alternate view. For example the main home screen may show brand such as Coke, Sprite, and so on, but also have buttons for "All fruit flavored", "All low calorie", and so on. Brands can be used for the main view while groups can be used for the alternate views without the need for adding special data into the nodes to tell the difference between them. As with brands, groups only exist as a convenience to the developer.

For systems that use BiB ingredients, the three layer dependency graph shown above is typically sufficient. The builder will automatically create the pump layer of nodes where the id of the nodes are the paths of the pumps. The builder can also automatically create the ingredients layer of nodes by calling addIngredientNodes(). This means that constructing a graph can be as simple as adding ingredients to beverage nodes and adding the beverage nodes to the beverage list.

This system can also support much more complex pour engines such as micro-dosing where a beverage may be defined by multiple different recipes and it is also possible to define beverages as combinations of other beverages. This can require six or more layers of dependencies, but once described, all other functionality is identical.

Since:
1.0
Version:
2023-02-07
  • Method Details

    • getPumps

      public List<Pump<?>> getPumps()
      Return the available pumps for use in the builder. These are pumps that have associated ingredients.
    • getIngredients

      public Set<BaseIngredient> getIngredients()
      Return the set of effective ingredients extracted from the list of pumps used to create the builder. This represents the list of ingredients currently available for pouring since if they're not assigned to a pump there is no way to pour them.
    • addIngredientNodes

      public void addIngredientNodes()
      Add ingredient nodes to the graph. This will iterate through all the pumps and add an ingredient node in the graph for each unique ingredient id. A dependency will be added between the ingredient and pump such that the ingredient availability will be the logical OR of all the pumps that contain the ingredient. For example, if two pumps have Coke, the ingredient node for Coke will be available if either of the two pumps are available to pour.

      This is a convenience method given that most applications build on the ingredient nodes instead of directly on pump nodes so that doubling up ingredients is transparently supported.

      If ingredient id's can collide with other id's, use the other version of this method that allows an id prefix to be specified.

    • addIngredientNodes

      public void addIngredientNodes(BevGraphBuilder.IncludePumpPredicate predicate)
      Identical to addIngredientNodes() except that the supplied predicate can remove individual pumps from the list as the graph is constructed. This can be useful if there are pumps that are not involved in beverage pouring and should be excluded from the bev graph.
      Parameters:
      predicate - optional predicate that determines if a given pump should be included
    • addIngredientNodes

      public void addIngredientNodes(String idPrefix, BevGraphBuilder.IncludePumpPredicate predicate)
      Add ingredient nodes to the graph. This will iterate through all the pumps and add an ingredient node in the graph for each unique ingredient id. A dependency will be added between the ingredient and pump such that the ingredient availability will be the logical OR of all the pumps that contain the ingredient. For example, if two pumps have Coke, the ingredient node for Coke will be available if either of the two pumps are available to pour.

      By default the node id of ingredient nodes will be the id of the ingredient. The idPrefix property can be provided to prefix all node id's in the event that ingredient id's collide with other types of id's. Pass null to use just the ingredientId as the node id.

      This is a convenience method given that most applications build on the ingredient nodes instead of directly on pump nodes so that doubling up ingredients is transparently supported.

      Parameters:
      idPrefix - optional prefix to add to ingredientId before using as node id.
      predicate - optional predicate that determines if a given pump should be included
    • addNode

      public void addNode(GraphNode node)
      Add a node to the graph. The node needs to be wired up using the various dependency api's using id based relationships. Just adding the node will have no effect unless it is used in dependencies with other nodes. The node can be added before or after the id is used in dependencies so long as all dependencies have associated nodes when the graph is built.
      Parameters:
      node - the node to add to the graph
    • addOptionalDependency

      public void addOptionalDependency(String parentId, String childId)
      Add a dependency such that the specified parent node depends on the child node. This dependency is optional and if the parent or child don't exist it will be ignored. This allows nodes to be created and linked to lower layers that may not match up exactly without needing to discover all the child nodes.

      For example, consider creating beverage nodes that depend on ingredients, but the available ingredients are only those inserted into pumps. Rather than checking if every ingredient actually exists, beverages can just use optional dependencies and those beverage that have missing ingredients will never become visible / available.

      Parameters:
      parentId - id of the parent node
      childId - id of the child node
    • addDependency

      public void addDependency(String parentId, String childId)
      Add a dependency such that the specified parent node depends on the child node.
      Parameters:
      parentId - id of the parent node
      childId - id of the child node
    • addDependency

      public void addDependency(GraphNode parent, GraphNode child)
      Add a dependency such that the specified parent node depends on the child node. This will also add both the parent and child nodes to the graph. This is a convenience method that is equivalent to: addDependency(parent.getId(), child.getId()); addNode(parent); addNode(child);
      Parameters:
      parent - parent node
      child - child node
    • addBeverage

      public void addBeverage(String bevId)
      Add an node id to the beverages group. This is a convenience method equivalent to: addToGroup(BevGraph.GROUP_BEVERAGES, bevId);
      Parameters:
      bevId - the id of the beverage node
    • addBeverage

      public void addBeverage(GraphNode node)
      Add a node to the graph and add the nodeId to the beverages group. This is a convenience method equivalent to: addToBeveragesGroup(node.getId()); addNode(node);
      Parameters:
      node - the beverage node
    • addBeverageToBrand

      public void addBeverageToBrand(String brandId, String bevId)
      In systems that rollup beverages to brands and brand availability needs to be available to the ui, calling this method will do the following:
      • If a node doesn't already exist for brandId, an OrNode will be created for the brand. This ensures that the brand availability reflects the logical OR of all the beverages added to the brand. If a custom brand node is required, simply add it to the builder before calling this method.
      • Adds the brand to the "brands" group so it will appear automatically in the availability endpoints.
      • The specified bevId will automatically be added to the beverages group. In applications that use brand this eliminates the need to explicitly add every beverage, although it will not cause any issues if they are added.
      • Adds a dependency between the brand and the beverage.
      • Tags the beverage id in the brand. This causes the list of beverage ids associated with the brand to appear in the brand availability data in the endpoint payloads. This allows ui code to identify beverages from brands without additional meta data.
      Parameters:
      brandId - the id of the brand
      bevId - the id of the beverage
    • addToGroup

      public void addToGroup(String group, String nodeId)
      Add a nodeId to a group with the specified name. If the group doesn't exist, it will be created. Groups are simply named collections of nodes that are available via endpoints. There are two internally defined groups:
      • beverages : This group always exists, even if empty. The purpose is to contain availability information about every beverage that can be poured.
      • brands : This group is created when addBeveragetoBrand() is used to rollup beverages into brands. The resulting group will contain the defined brands and the brand objects will contain the associated beverage ids.
      Groups can be used to represent availability information for any collection of nodes. For example, in a system that uses beverages and flavors, where flavors are added independent to the selected beverage, a "flavors" group can be used to contain the availability of flavors which allows the ui to be entirely data driven from just availability information.
      Parameters:
      group - name of the group
      nodeId - the id of the node
    • addTagIdToNode

      public void addTagIdToNode(String nodeId, String tagId)
      Nodes that are contained within groups have their availability information exposed via endpoints. In some cases it can be convenient for this availability information to include links to other nodes. For example, in applications where beverages rollup to brands, it is convenient for the brand availability to contain the list of beverages that are in the brand. This can be done by adding a tagId to a node. In the case of brands, the beverage id's can be added as tagId's to the brand node.

      Any node can contain tags but the tagId list will only be accessible for nodes that are exposed to endpoints by virtue of being in a group.

      Parameters:
      nodeId - the node to tag
      tagId - the tagId to add
    • build

      public BevGraph build()
      Build the dependencies into a BevGraph.