How to use Shared Flows to achieve reuse and development artifact isolation?

As an API developer, I want to structure project related API proxies for reuse and isolation of development artifacts.

There are a few ways to achieve reuse:

  1. Reuse via CI/CD common source code artifacts with separate API proxies
  2. Proxy chaining from top level proxy to lower level proxies via route rules
  3. Reuse via Shared Flows with separate API proxies

In this case, I want to exploit Shared Flows via Flow Callouts. Consider a typical scenario consisting of multiple functional API areas (2 shown for brevity):

  • Catalog - product, services, accessories (use case for multiple basepaths)
  • Order Management - orders

API requirements:

  1. Common basepath: /ecommerce/v1
  2. Support for CORS
  3. OAuth security
  4. Traffic Management
  5. Logging
  6. Consistent error handling and responses

What's the best way to use Shared Flows, as fine grained Flow Callouts or coarse grained structures?

Solved Solved
1 2 1,329
1 ACCEPTED SOLUTION

The Proxies

To support a common basepath across multiple proxies, I'm using overlapping basepaths for each proxy and a catch all for paths that don't match in the funcational proxies and in the "catch all" proxy.

Catalog Proxy:

Proxy Endpoints and flows:
  products
    Basepath = /ecommerce/v1/products
      Conditional Flows
      Catch all - non matched paths
  accessories
    Basepath = /ecommerce/v1/accessories
      Conditional Flows
      Catch all - non matched paths
  services
    Basepath = /ecommerce/v1/services
      Conditional Flows
      Catch all - non matched paths
Target Endpoints
  default
    {CATALOG-TARGET-SERVER}/backend/catalog-service

Order Management Proxy:

Proxy Endpoints and flows:
  orders
    Basepath = /ecommerce/v1/orders
      Conditional Flows
      Catch all - non matched paths
Target Endpoints:
  default
    {ORDER-TARGET-SERVER}/backend/order-service

Catch All Proxy (no route):

Proxy Endpoints and flows:
  default
    Basepath = /ecommerce
      Catch all - non matched paths

The Shared Flows

Here's one way to structure the Shared Flows, each bullet item is a Step with a Policy.

ProxyPreflow - Assumes common requirements for all endpoints in the proxy.

  • CORS Pre Flow (Shared Flow)
  • OAuth Verify Token
  • Spike arrest
  • Quota
  • Request logging

CatchAll - At end of conditional flows

  • Assign Error Variables
  • Raise Fault

ProxyFaultRules - Catch faults and assign error values for use in ProxyDefaultFaultRule. Uses conditional steps for each of the faults (based on this fault handling pattern).

  • Invalid OAuth (InvalidAccessToken, etc.)
  • Spike arrest
  • Quota

ProxyDefaultFaultRule - Uses error variables from ProxyFaultRules steps

  • Error logging
  • Standard error response
  • CORS Post Flow (shared flow)

ProxyPostFlow - Response logging

  • CORS Post Flow (shared flow)

The following Shared Flows are reusable across other proxies and also demonstrates that Shared Flows can call other Shared Flows.

CORSPreFlow

  • Javascript Callout to extract headers
  • Raise Fault 200 OK (when Verb=OPTIONS)

CORSPostFlow

  • Assign Message to set CORS headers from JS in CORSPreFlow

As you can see there’s not much to the design of each API proxy, just mediating between the proxy API flows and the target APIs. The rest is done in the shared flows. Each proxy merely references the Shared Flows via Flow Callouts.

Its also easy to imagine how quickly new proxies can be developed for the project, just copy and paste the ProxyEndpoint and adjust the flows.

For error handling there is a single Fault Rule with a Flow Callout step to the ProxyFaultRules Shared Flow. Then in the DefaultFaultRule (AlwaysEnforce = true) there is a single Flow Callout step to the DefaultProxyFaultRule Shared Flow.

Benefits of this approach:

  1. Modular design provides development artifact isolation
  2. Maximizes reuse of shared artifacts through shared flows
  3. API proxy-as-a-template to easily support new proxies (e.g. carts)
  4. No code manipulation during CI/CD builds - a common corporate requirement

Risks of this approach:

  1. Runtime dependency - changing a shared flow affects all consumers

Additional opportunities:

  • Flow Hooks to eliminate PreFlow and PostFlow Flow Callouts, but not for error handling.
  • Shared Flows for categories of faults rather than just one for all faults.
  • Shared Flows in the Target Endpoints.

Here's the Proxy Endpoint for orders proxy with the Flow Callouts. Note that for the catalog proxy this structure is replicated for each of the three proxy endpoints (products, accessories, services).

<ProxyEndpoint name="default">
    <Description/>
    <DefaultFaultRule>
        <AlwaysEnforce>true</AlwaysEnforce>
        <Step>
            <Name>FC-DefaultFaultRule</Name>
        </Step>
    </DefaultFaultRule>
    <FaultRules>
        <FaultRule name="Shared Fault Rules">
            <Step>
                <Name>FC-ProxyFaultRules</Name>
            </Step>
        </FaultRule>
    </FaultRules>
    <PreFlow name="PreFlow">
        <Request>
            <Step>
                <Name>FC-ProxyPreFlow</Name>
            </Step>
            <Step>
                <Name>KV-GetOrdersConfigValues</Name>
            </Step>
        </Request>
        <Response/>
    </PreFlow>
    <Flows>
        <-- Conditional flows removed for brevity -->
        <Flow name="catchAll">
            <Request>
                <Step>
                    <Name>FC-CatchAll</Name>
                </Step>
            </Request>
            <Response/>
        </Flow>
    </Flows>
    <PostFlow name="PostFlow">
        <Request/>
        <Response>
            <Step>
                <Name>FC-ProxyPostFlow</Name>
            </Step>
        </Response>
    </PostFlow>
    <HTTPProxyConnection>
        <BasePath>/ecommerce/v1/orders</BasePath>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>
</ProxyEndpoint>

View solution in original post

2 REPLIES 2

The Proxies

To support a common basepath across multiple proxies, I'm using overlapping basepaths for each proxy and a catch all for paths that don't match in the funcational proxies and in the "catch all" proxy.

Catalog Proxy:

Proxy Endpoints and flows:
  products
    Basepath = /ecommerce/v1/products
      Conditional Flows
      Catch all - non matched paths
  accessories
    Basepath = /ecommerce/v1/accessories
      Conditional Flows
      Catch all - non matched paths
  services
    Basepath = /ecommerce/v1/services
      Conditional Flows
      Catch all - non matched paths
Target Endpoints
  default
    {CATALOG-TARGET-SERVER}/backend/catalog-service

Order Management Proxy:

Proxy Endpoints and flows:
  orders
    Basepath = /ecommerce/v1/orders
      Conditional Flows
      Catch all - non matched paths
Target Endpoints:
  default
    {ORDER-TARGET-SERVER}/backend/order-service

Catch All Proxy (no route):

Proxy Endpoints and flows:
  default
    Basepath = /ecommerce
      Catch all - non matched paths

The Shared Flows

Here's one way to structure the Shared Flows, each bullet item is a Step with a Policy.

ProxyPreflow - Assumes common requirements for all endpoints in the proxy.

  • CORS Pre Flow (Shared Flow)
  • OAuth Verify Token
  • Spike arrest
  • Quota
  • Request logging

CatchAll - At end of conditional flows

  • Assign Error Variables
  • Raise Fault

ProxyFaultRules - Catch faults and assign error values for use in ProxyDefaultFaultRule. Uses conditional steps for each of the faults (based on this fault handling pattern).

  • Invalid OAuth (InvalidAccessToken, etc.)
  • Spike arrest
  • Quota

ProxyDefaultFaultRule - Uses error variables from ProxyFaultRules steps

  • Error logging
  • Standard error response
  • CORS Post Flow (shared flow)

ProxyPostFlow - Response logging

  • CORS Post Flow (shared flow)

The following Shared Flows are reusable across other proxies and also demonstrates that Shared Flows can call other Shared Flows.

CORSPreFlow

  • Javascript Callout to extract headers
  • Raise Fault 200 OK (when Verb=OPTIONS)

CORSPostFlow

  • Assign Message to set CORS headers from JS in CORSPreFlow

As you can see there’s not much to the design of each API proxy, just mediating between the proxy API flows and the target APIs. The rest is done in the shared flows. Each proxy merely references the Shared Flows via Flow Callouts.

Its also easy to imagine how quickly new proxies can be developed for the project, just copy and paste the ProxyEndpoint and adjust the flows.

For error handling there is a single Fault Rule with a Flow Callout step to the ProxyFaultRules Shared Flow. Then in the DefaultFaultRule (AlwaysEnforce = true) there is a single Flow Callout step to the DefaultProxyFaultRule Shared Flow.

Benefits of this approach:

  1. Modular design provides development artifact isolation
  2. Maximizes reuse of shared artifacts through shared flows
  3. API proxy-as-a-template to easily support new proxies (e.g. carts)
  4. No code manipulation during CI/CD builds - a common corporate requirement

Risks of this approach:

  1. Runtime dependency - changing a shared flow affects all consumers

Additional opportunities:

  • Flow Hooks to eliminate PreFlow and PostFlow Flow Callouts, but not for error handling.
  • Shared Flows for categories of faults rather than just one for all faults.
  • Shared Flows in the Target Endpoints.

Here's the Proxy Endpoint for orders proxy with the Flow Callouts. Note that for the catalog proxy this structure is replicated for each of the three proxy endpoints (products, accessories, services).

<ProxyEndpoint name="default">
    <Description/>
    <DefaultFaultRule>
        <AlwaysEnforce>true</AlwaysEnforce>
        <Step>
            <Name>FC-DefaultFaultRule</Name>
        </Step>
    </DefaultFaultRule>
    <FaultRules>
        <FaultRule name="Shared Fault Rules">
            <Step>
                <Name>FC-ProxyFaultRules</Name>
            </Step>
        </FaultRule>
    </FaultRules>
    <PreFlow name="PreFlow">
        <Request>
            <Step>
                <Name>FC-ProxyPreFlow</Name>
            </Step>
            <Step>
                <Name>KV-GetOrdersConfigValues</Name>
            </Step>
        </Request>
        <Response/>
    </PreFlow>
    <Flows>
        <-- Conditional flows removed for brevity -->
        <Flow name="catchAll">
            <Request>
                <Step>
                    <Name>FC-CatchAll</Name>
                </Step>
            </Request>
            <Response/>
        </Flow>
    </Flows>
    <PostFlow name="PostFlow">
        <Request/>
        <Response>
            <Step>
                <Name>FC-ProxyPostFlow</Name>
            </Step>
        </Response>
    </PostFlow>
    <HTTPProxyConnection>
        <BasePath>/ecommerce/v1/orders</BasePath>
        <VirtualHost>secure</VirtualHost>
    </HTTPProxyConnection>
    <RouteRule name="default">
        <TargetEndpoint>default</TargetEndpoint>
    </RouteRule>
</ProxyEndpoint>

Great Answer @Kurt Kanaskie , Very well detailed one, Thank you for sharing same with community ! +1