The need for global endpoints
Tags: serverless, aws, cloudnative, programming
Published: 2022-08-31 05:04:51

In this post, I will look at the global endpoints concept and share my point of view on what is needed to build a multi-region application.

As a part of the series [The Multi-Region road](https://dev.to/ymwjbxxq/series/17479), you can check out the other parts:

- [Part 1](https://dev.to/aws-builders/the-multi-region-road-160h) - A reflection on what to consider before starting a multi-region architecture.
- [Part 2](https://dev.to/aws-builders/the-multi-region-road-cloudfront-25n8) - CloudFront failover configuration.
- [Part 3](https://dev.to/aws-builders/the-multi-region-road-amazon-api-gateway-59l0) - Amazon API Gateway HTTP API failover and latency configuration.
- [Part 4](https://dev.to/aws-builders/the-multi-region-road-amazon-dynamodb-41d5) - Amazon DynamoDB Global Tables.

# The Deployment Nightmare

When building a multi-region application, there are many things to consider, and I am not here to tell you that it is simple. Still, some services facilitate the architecture, and from this series, I have already used a few but, to be honest, AWS will not make it easier, especially if you want to deploy them. 

Amazon CloudFront is the service if I want to cache close to the end-user. It comes with failover criteria where I can add a series of regions, and based on the error, CloudFront will hit the custom origin associated.

```
        OriginGroups:
          Items:
            - Id: Failover
              FailoverCriteria: 
                StatusCodes:
                  Items:
                    - 502 # Bad Gateway Exception
                    - 503 # Service Unavailable Exception
                    - 504 # Endpoint Request Timed-out Exception
                  Quantity: 3
              Members: 
                Items: 
                  - OriginId: region1
                  - OriginId: region2
                  - OriginId: region3
                Quantity: 3
          Quantity: 1
        Origins:
          - Id: Region1
            DomainName: !Ref Region1OriginEndpoint
            ....
          - Id: Region2
            DomainName: !Ref Region2OriginEndpoint
            ....
          - Id: Region3
            DomainName: !Ref Region3OriginEndpoint
            ....
``` 

Each origin point to something, and it could easily be a microservice like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4uq8pdwotdvroppi46v.png) 

This microservice composed of Amazon API Gateway and AWS Lambda must be deployed before Amazon CloudFront in each region. Imagine doing this in all +20 regions. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fh9kjp98i2bpt6nhuyx8.png) 

Each microservice deployment must have an export name that must be passed dynamically to the Amazon CloudFront template. In other words, I must load the export name and pass them as override parameters:

```
- REGION1_ORIGIN_ENDPOINT=$(aws cloudformation list-exports --region $AWS_REGION1 --o text --query "Exports[?Name=='${MY_MICROSERVICE_STACKNAME}-ApiDomainName'].Value")
- REGION2_ORIGIN_ENDPOINT=$(aws cloudformation list-exports --region $AWS_REGION2 --o text --query "Exports[?Name=='${MY_MICROSERVICE_STACKNAME}-ApiDomainName'].Value")

 - sam deploy --template-file template.yml
      --stack-name ${STACK_NAME}
      --parameter-overrides Region1OriginEndpoint=${REGION1_ORIGIN_ENDPOINT} Region2OriginEndpoint=${REGION2_ORIGIN_ENDPOINT}
      --capabilities CAPABILITY_NAMED_IAM
      --no-confirm-changeset
      --no-fail-on-empty-changeset
      --resolve-s3
...
``` 

The same also applies if you want to use Amazon Route 53. Again, I must deploy all Amazon API Gateway or Application Load Balancer references in every single region and pass the references to Amazon Route 53.

**What about AWS Lambda and Amazon DynamoDB Global Tables?**

We must pass the table name to the Lambda to allow a connection between the AWS Lambda function and Amazon DynamoDB.

```
Policies:
        - AWSLambdaBasicExecutionRole
        - Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action: 
                - dynamodb:GetItem
              Resource: !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${MyTableName}
Environment:
        Variables:
          MY_TABLE_NAME: !Sub ${MyTableName}
``` 

Using Amazon DynamoDB Global Tables does not really help in terms of deployment:


```
  MyTable:
    Type: AWS::DynamoDB::GlobalTable
    Properties:
      ...
      Replicas:
        - Region: region1
        - Region: region2
        - Region: region3

Outputs:
  MyTableName:
    Value: !Ref MyTable
    Export:
      Name: !Sub ${AWS::StackName}-MyTableName
``` 

When I deploy a global service, I must select a region, and the output parameter is present only in that region. So to get that global value, I must point to a specific region.


# The not perfect Global Era

AWS does not make it easier for builders to deploy multi-region applications. For example, we have global services, but they need to use regional references. 

Until now, there is no way to automatically deploy a service like Amazon API Gateway, AWS Lambda, Amazon SQS, Amazon SNS, AWS Step Functions etc., in multiple-region. So what I do, for example, with GitLab is the use of [parallel](https://docs.gitlab.com/ee/ci/yaml/#parallel) option to run a job multiple times in parallel in a single pipeline with a matrix for passing different variable values for each instance of the job:

```
.rust:service: &deployMyService
  stage: myStage
  needs:
    - build:rust
  script:
    # Switch to proper AWS role
    - ...
    # Go to yml folder
    - ...
    # Import global table name
    - MY_TABLE_NAME=$(aws cloudformation list-exports --region $AWS_REGION_GERMANY --o text --query "Exports[?Name=='${STACK_NAME_DYNAMO}-MyExportName'].Value")
    - sam deploy --template-file template.yml
      --stack-name ${STACK_NAME}
      --parameter-overrides StageName=${STAGE} MyTableName=${MY_TABLE_NAME}
      --capabilities CAPABILITY_NAMED_IAM
      --no-confirm-changeset
      --no-fail-on-empty-changeset
      --resolve-s3
      --region $AWS_REGION
  parallel:
    matrix:
      - AWS_REGION:
        - eu-west-1
        - eu-central-1
        - eu-west-2
```

This ease the automation of multi-region deployment until Too Many SAM Deploys in Parallel results in Rate Limit.

Things are changing, and we have not just the Amazon DynamoDB Global Tables but also 
[S3 Multi-Region Access Points](https://aws.amazon.com/blogs/aws/s3-multi-region-access-points-accelerate-performance-availability/) that provide a single global endpoint to access a data set that spans multiple S3 buckets in different AWS Regions, and this allows to dynamically route client requests across AWS Regions to the S3 bucket with the lowest latency. 

Recently AWS also added the [Global endpoints for Amazon EventBridge](https://aws.amazon.com/blogs/compute/introducing-global-endpoints-for-amazon-eventbridge/) that allow failing over event ingestion automatically to a secondary Region during service disruptions. However, it is not a really global service but more a failover configuration between two regions. A complete working example can be found on [ServerlessLand](https://serverlessland.com/patterns/eventbridge-sqs-global-endpoint-sam).

The problem with Global endpoints for Amazon EventBridge are the following:

- Can be used only from a Lambda
- I cannot use it in conjunction with Amazon API Gateway or other services

Because I cannot automatically connect the EventBridge Global Endpoints to anything, I need to deploy a bus in the specific region and do all the plumbing to connect it to services like SQS, Lambda, etc.


## My Wish

I wish to configure all Serverless services as Global with:

- A list of regions
- A failover order criteria

For example something like:

```
  MyApi:
    Type: AWS::Serverless::GlobalApi
    Properties:
      Regions:
        - eu-central-1
        - eu-central-2
        - eu-west-1
        - eu-west-2
        - eu-west-3
      FailoverOrder:
        - eu-central-1
        - eu-west-1
        - eu-central-2
        - eu-west-3
        - eu-west-2

  MyFunction:
    Type: AWS::Serverless::GlobalFunction
    Properties:
      Regions:
        - eu-central-1
        - eu-central-2
        - eu-west-1
        - eu-west-2
        - eu-west-3
      FailoverOrder:
        - eu-central-1
        - eu-west-1
        - eu-central-2
        - eu-west-3
        - eu-west-2
      Events:
        ApiEvents:
          Type: Api
          Properties:
            Path: /something
            Method: GET
            RestApiId: !Ref MyApi

Outputs:
  ApiDomainName:
    Description: The domain name of the endpoint.
    Value: !Sub "${MyApi}.execute-api.${AWS::Region}.amazonaws.com"
    Export:
      Name: DomainName
``` 

### Why

With the snippet above, I deployed once in multi-regions and not N times, and I declared the order in which the AWS network should handle maybe a failure of a Region or possibly route traffic to the region that provides the best latency. If AWS would drive for excellent would allow creating the criteria based on:

- Failover routing policy
- Geolocation routing policy
- Weighted routing policy
and so on

Precisely the same as [Amazon Route 53](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html)

The deployment stack should automatically be deployed in each region, and CloudFormation must understand how to get the reference in the same region. Now using the intrinsic function Fn::ImportValue  that returns the value of an output exported by another stack or a new one Fn:GlobalValue, CloudFormation could become as simple as:

```
Origins:
          - Id: Region1
            DomainName: !ImportValue DomainName
            ....
          - Id: Region2
            DomainName: !ImportValue DomainName
            ....
          - Id: Region3
            DomainName: !ImportValue DomainName
            ....

``` 

This is why a Global to Global integration is needed:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pr2hrwqoen5rtj3cp2t1.png) 

Considering that I can integrate many services with each other, I should also be able to do much with the Global services without using a Lambda in the middle.
CloudFormation or AWS SAM should do the entire job.
I configure once for specific regions, adding the references between Global Endpoints, and let AWS creates the regional resource and references needed for the multi-region deployment. 

# Conclusion

For now, my wish cannot be satisfied because the Global All concept does not exist and is most likely a need for a few. For example, one of [Six advantages of cloud computing](https://docs.aws.amazon.com/whitepapers/latest/aws-overview/six-advantages-of-cloud-computing.html) is **Go global in minutes**. It is relatively easy in respect of everything on-premises, but it is not so easy once you are in the cloud. 

Suppose AWS is planning to give us the following Global Endpoints, such as SQS or SNS, APIGW, Lambda, etc. In that case, I wish for out-of-the-box integration with each other and a much simpler way to deploy. 

Sadly I think without better CloudFormation or heavy change to this essential service, a truly **Go global in minutes** is still far away. Until then, multi-region deployment will be cucumber and complex, but I will be grateful for any new upcoming Global services that will make my life easier building a Multi-Region application.