Skip to content

Commit e38b53d

Browse files
author
Venugopalan Vasudevan
committed
Initial commit
1 parent 19834ff commit e38b53d

File tree

16 files changed

+1146
-9
lines changed

16 files changed

+1146
-9
lines changed

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#folders#
2+
.idea
3+
/target
4+
.classpath
5+
.project
6+
.settings/
7+
8+
# Packaged files #
9+
*.jar
10+
*.war
11+
*.ear
12+
*.iml

Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM maven:3.6.1-amazoncorretto-8 as build
2+
3+
ENV MAVEN_VERSION 3.6.1
4+
ENV MAVEN_HOME /usr/lib/mvn
5+
ENV PATH $MAVEN_HOME/bin:$PATH
6+
7+
WORKDIR /workspace/app
8+
9+
10+
COPY pom.xml .
11+
COPY src src
12+
RUN mvn install -DskipTests
13+
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
14+
15+
FROM maven:3.6.1-amazoncorretto-8
16+
ARG DEPENDENCY=/workspace/app/target/dependency
17+
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
18+
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
19+
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
20+
ENTRYPOINT ["java","-cp","app:app/lib/*","com.amazonaws.samples.appconfig.movies.MoviesApplication"]

NOTICE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT-0

README.md

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,103 @@
1-
## My Project
1+
# AWS AppConfig Java Sample
22

3-
TODO: Fill this README out!
3+
## **Overview**
44

5-
Be sure to:
5+
This is a demo of the [AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html) Java application as explained in the blog post [Application configuration deployment to container workloads using AWS AppConfig](https://aws.amazon.com/blogs/mt/application-configuration-deployment-to-container-workloads-using-aws-appconfig). This demo shows how to integrate a Java Microservices application with AWS AppConfig service along with implementing an in-memory cache to efficiently manage the application configuration.
66

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
7+
[AWS AppConfig](https://docs.aws.amazon.com/appconfig/latest/userguide/what-is-appconfig.html) helps AWS customers to quickly roll out application configurations across applications hosted on EC2 instances, containers, AWS Lambda, mobile apps, IoT devices, and on-premise servers in a validated, controlled and monitored way.
98

10-
## Security
9+
This demo explains
1110

12-
See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
1311

14-
## License
12+
1. How to separate application configuration from application code for a containerized application.
13+
2. Use AWS AppConfig to manage and deploy the application configuration.
14+
3. How to automate and efficiently manage application configurations in a containerized application.
15+
1516

16-
This library is licensed under the MIT-0 License. See the LICENSE file.
17+
This demo uses Cloudformation templates to deploy an [Amazon Elastic Container Service](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html) Cluster and a [AWS Fargate](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html) Task. Users would clone this repository, build a docker image and push to [Amazon Elastic Container Registry](https://docs.aws.amazon.com/AmazonECR/latest/userguide/what-is-ecr.html) and interact with the AWS AppConfig Service.
1718

19+
This application has a Caching layer built in to cache the responses from AWS AppConfig Service. The Subsequent calls to fetch the configuration value checks the cache first and returns the response from the cache. If the cache is empty, then it makes a call to AWS AppConfig API to fetch the value. The Cache expiry is based on TTL set in properties.
20+
21+
## **Installation Instructions**
22+
23+
### **STEP 1: Create application, environments and configuration profile in AWS AppConfig**
24+
25+
1. To get started, go to [AWS AppConfig](https://console.aws.amazon.com/systems-manager/appconfig) on the [AWS Console](https://console.aws.amazon.com/console/home)
26+
2. On the [AppConfig console](https://console.aws.amazon.com/systems-manager/appconfig) , click Create Configuration Data and specify the name of the application. You can add an optional description and apply tags to the application.
27+
3. Once the application is created, you will then be redirected to a page with Environments and Configuration Profiles tabs. Let’s start by creating an environment by clicking Create environment and specifying the environment name and optional description. You can also optionally add tags and configure CloudWatch alarms for this environment.
28+
4. Next, let’s set up a configuration profile. Select the Configuration Profiles tab, and click Create configuration profile. Provide the name of the configuration profile and an optional description and click Next.
29+
5. Next, select *AWS AppConfig hosted configuration* as the configuration source and specify the content of the configuration data. Within AWS AppConfig hosted configurations, you can store configurations either as text or in JSON or YAML formats. For the purposes of this blog, we will create an application configuration in JSON with two parameters which will be used in the Container application code.
30+
31+
```
32+
{
33+
"boolEnableFeature": true,
34+
"intItemLimit": 5
35+
}
36+
```
37+
38+
1. Clicking Next, you can optionally add validators to validate the configuration. More information on adding validators can be found [here](https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-creating-configuration-and-profile-validators.html). Next, create the configuration by clicking Create configuration profile.
39+
2. Next, you can deploy the configuration by clicking Start deployment
40+
3. Choose the Environment , Hosted Configuration Version, Deployment Strategy and an Optional Description to Start the deployment process.
41+
4. You can either create a custom Deployment Strategy by clicking Create Deployment Strategy or select one of the pre-defined Deployment Strategies provided by AWS AppConfig. You can read more about the components of the deployment strategies [here](https://docs.aws.amazon.com/systems-manager/latest/userguide/appconfig-creating-deployment-strategy.html). For the purposes of this demo, we are selecting the “*AppConfig.Linear50PercentEvery30Seconds*” deployment strategy.
42+
43+
44+
45+
### STEP 2: Clone the Repository
46+
47+
1. Git clone this Repository
48+
49+
### STEP 3: Containerize the Java Microservice into a Docker container, publish to Amazon ECR and then deploy the Container application into AWS ECS Fargate
50+
51+
1. Create base application stack using AWS CloudFormation
52+
53+
* Open [CloudFormation console](https://console.aws.amazon.com/cloudformation/home) and click on “Create stack”, selecting “With new resources” option.
54+
* In the next screen, under the “Specify template” section choose “Upload template file“, and provide the file you downloaded from the repo */templates/ecs-cluster.yml.*
55+
* Click next, give the stack a name like “ECSCluster-dev“, and choose dev as value for the Environment parameter.Click next, optionally define your tags, and click next again. On the last screen, don’t forget to tick the check box in the “Capabilities” section, and finally click on “Create stack” button.
56+
57+
1. Upload the Project Image to Amazon Elastic Container Repository
58+
59+
* Open the Amazon Elastic Container repository created from previous step and choose "View push commands".
60+
* Follow the steps displayed to upload the project (these steps work only when you use AWS CLI version 1.7 or later).
61+
* When the upload completes, copy the URL of the image in the repository. Use this URL as a Input parameter to the cloud formation template described in the section section.
62+
63+
64+
65+
1. Create Fargate Task, ECS Service and Load Balancer using AWS CloudFormation
66+
67+
* Open [CloudFormation console](https://console.aws.amazon.com/cloudformation/home) and click on “Create stack”, selecting “With new resources” option.
68+
* In the next screen, under the “Specify template” section choose “Upload template file“, and provide the file you downloaded from the repo */templates/fargate-task.yml.*
69+
* Click next, give a name to the stack like “fargate-task-dev”. Choose “dev” as value for the Environment parameter. Provide the image URL obtained in the previous step for the ImageUrl parameter and leave the rest of the parameters as default. Click next and optionally define your tags. Click next again. On the last screen, don’t forget to tick the check box in the “Capabilities” section, and finally click on “Create stack” button.
70+
71+
### STEP 4 : Verify the deployed application, update the AppConfig Configuration data and deploy configuration to the application
72+
73+
1. Navigate to [AWS CloudFormation Console](https://console.aws.amazon.com/cloudformation/home) and open the fargate-task-dev stack you created
74+
2. Click on Outputs and copy the ExternalUrl for the Loadbalancer
75+
3. Verify the application by using the External URL for the Load Balancer. *http://<load balancer dns>/movies/getMovies*
76+
4. Next, we will change the configuration value in the AWS AppConfig and see how it will be reflected in the container application.
77+
5. Open the [AWS AppConfig console](https://console.aws.amazon.com/systems-manager/appconfig) , click on your Application and go to Configuration Profiles tab and click on the Configuration Profile you created
78+
6. Click on Create under Hosted configuration versions, this will open a new screen where you can edit the Configuration data.
79+
7. Edit the Configuration value and click Create hosted configuration version button.
80+
8. Next, click start deployment and choose the Environment, latest hosted configuration version, Deployment Strategy and an optional description to start the deployment process.
81+
9. Once the deployment is complete, visit the application URL again to see the changes reflected immediately.
82+
10. Note that this change did not require the container application to be restarted since the application retrieved the updated value in the subsequent call to AWS AppConfig.
83+
84+
## Cleanup
85+
86+
Delete all the resources created in throughout this process and prevent additional costs.
87+
88+
**AppConfig**
89+
90+
1. Hosted configuration
91+
2. Configuration Profile
92+
3. Environment
93+
4. Application
94+
95+
**Base Container Application and Fargate Task**
96+
97+
1. Navigate to [AWS CloudFormation Console](https://console.aws.amazon.com/cloudformation/home)
98+
2. Select the fargate-task-dev stack and click delete
99+
3. Select the ECSCluster-dev stack and click delete
100+
101+
## Licence
102+
103+
This sample code is licensed under the MIT-0 License. See the LICENSE file.

build.gradle

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
buildscript {
2+
repositories {
3+
maven {
4+
url "https://plugins.gradle.org/m2/"
5+
}
6+
mavenCentral()
7+
}
8+
dependencies {
9+
classpath('org.springframework.boot:spring-boot-gradle-plugin:2.3.0.RELEASE')
10+
}
11+
}
12+
13+
apply plugin: 'java'
14+
apply plugin: 'eclipse'
15+
apply plugin: 'idea'
16+
apply plugin: 'org.springframework.boot'
17+
apply plugin: 'io.spring.dependency-management'
18+
19+
group = 'springio'
20+
21+
22+
repositories {
23+
mavenCentral()
24+
}
25+
26+
sourceCompatibility = 1.8
27+
targetCompatibility = 1.8
28+
29+
30+
dependencies {
31+
compile("org.springframework.boot:spring-boot-starter-web")
32+
testCompile("org.springframework.boot:spring-boot-starter-test")
33+
implementation platform('software.amazon.awssdk:bom:2.14.27')
34+
implementation 'software.amazon.awssdk:appconfig'
35+
compile("org.json:json:20200518")
36+
testImplementation group: 'junit', name: 'junit', version: '4.11'
37+
}
38+

pom.xml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>org.springframework</groupId>
7+
<artifactId>movie-service</artifactId>
8+
<version>0.1.0</version>
9+
10+
<parent>
11+
<groupId>org.springframework.boot</groupId>
12+
<artifactId>spring-boot-starter-parent</artifactId>
13+
<version>2.0.5.RELEASE</version>
14+
</parent>
15+
<dependencyManagement>
16+
<dependencies>
17+
<dependency>
18+
<groupId>software.amazon.awssdk</groupId>
19+
<artifactId>bom</artifactId>
20+
<version>2.14.27</version>
21+
<type>pom</type>
22+
<scope>import</scope>
23+
</dependency>
24+
</dependencies>
25+
</dependencyManagement>
26+
27+
<dependencies>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-web</artifactId>
31+
</dependency>
32+
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/appconfig -->
33+
<dependency>
34+
<groupId>software.amazon.awssdk</groupId>
35+
<artifactId>appconfig</artifactId>
36+
</dependency>
37+
<!-- https://mvnrepository.com/artifact/org.json/json -->
38+
<dependency>
39+
<groupId>org.json</groupId>
40+
<artifactId>json</artifactId>
41+
<version>20200518</version>
42+
</dependency>
43+
</dependencies>
44+
45+
<properties>
46+
<java.version>1.8</java.version>
47+
</properties>
48+
49+
<build>
50+
<plugins>
51+
<plugin>
52+
<groupId>org.springframework.boot</groupId>
53+
<artifactId>spring-boot-maven-plugin</artifactId>
54+
</plugin>
55+
<plugin>
56+
<groupId>org.apache.maven.plugins</groupId>
57+
<artifactId>maven-compiler-plugin</artifactId>
58+
<version>3.1</version>
59+
<configuration>
60+
<source>${java.version}</source>
61+
<target>${java.version}</target>
62+
</configuration>
63+
</plugin>
64+
</plugins>
65+
</build>
66+
</project>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package com.amazonaws.samples.appconfig;
2+
3+
import com.amazonaws.samples.appconfig.cache.ConfigurationCache;
4+
import com.amazonaws.samples.appconfig.cache.ConfigurationCacheItem;
5+
import com.amazonaws.samples.appconfig.model.ConfigurationKey;
6+
import software.amazon.awssdk.services.appconfig.AppConfigClient;
7+
import software.amazon.awssdk.services.appconfig.model.GetConfigurationRequest;
8+
import software.amazon.awssdk.services.appconfig.model.GetConfigurationResponse;
9+
10+
import java.time.Duration;
11+
import java.util.Optional;
12+
13+
14+
public class AppConfigUtility {
15+
16+
private final AppConfigClient client;
17+
private final ConfigurationCache cache;
18+
private final Duration cacheItemTtl;
19+
private final String clientId;
20+
21+
/**
22+
* Constructor for AppConfigUtility.
23+
*
24+
* @param client client for retrieving configurations from the AppConfig API.
25+
* @param configurationCache cache for configurations returned by the AppConfig API.
26+
* @param cacheItemTtl items are refreshed according to this ttl. default is 30 secs.
27+
* @param clientId unique clientId that is sent to the AppConfig API. Default is a random UUID.
28+
*/
29+
public AppConfigUtility(final AppConfigClient client,
30+
final ConfigurationCache configurationCache,
31+
final Duration cacheItemTtl,
32+
final String clientId) {
33+
34+
this.client = client;
35+
this.cache = configurationCache;
36+
this.cacheItemTtl = cacheItemTtl;
37+
this.clientId = clientId;
38+
39+
}
40+
41+
/**
42+
* Returns the AppConfig from the cache or from the API based on the TTL.
43+
*
44+
* @param configurationKey specifies the application name, environment name, and the configuration name of the configuration to be
45+
* * retrieved.
46+
* @return GetConfigurationResponse Containing the content, content type, and version of the configuration.
47+
*/
48+
public GetConfigurationResponse getConfiguration(final ConfigurationKey configurationKey) {
49+
final ConfigurationCacheItem<GetConfigurationResponse> result = Optional.ofNullable(cache.get(configurationKey))
50+
.map(item -> {
51+
if (item.isRefreshNeeded()) {
52+
return getConfigurationFromApiAndApplyToCache(configurationKey, item, item.getValue().configurationVersion());
53+
} else {
54+
return item;
55+
}
56+
}).orElseGet(() -> getConfigurationFromApiAndApplyToCache(configurationKey, null, null));
57+
if (result.getValue() == null && result.getException() != null) {
58+
throw result.getException();
59+
}
60+
return result.getValue();
61+
}
62+
63+
64+
protected ConfigurationCacheItem<GetConfigurationResponse> getConfigurationFromApiAndApplyToCache(
65+
final ConfigurationKey configurationKey,
66+
final ConfigurationCacheItem<GetConfigurationResponse> existingItem,
67+
final String version) {
68+
try {
69+
final GetConfigurationResponse result = client.getConfiguration(GetConfigurationRequest.builder()
70+
.application(configurationKey.getApplication())
71+
.environment(configurationKey.getEnvironment())
72+
.configuration(configurationKey.getConfiguration())
73+
.clientId(this.clientId)
74+
.clientConfigurationVersion(version)
75+
.build());
76+
77+
final ConfigurationCacheItem<GetConfigurationResponse> item = new ConfigurationCacheItem<>(cacheItemTtl);
78+
if (result.content() != null) {
79+
item.setValue(result);
80+
}
81+
82+
if (existingItem == null || existingItem.getValue() == null
83+
|| (item.getValue() != null
84+
&& !existingItem.getValue().configurationVersion().equals(item.getValue().configurationVersion()))) {
85+
item.calculateAndSetRefreshTime();
86+
cache.put(configurationKey, item);
87+
return item;
88+
} else {
89+
90+
existingItem.calculateAndSetRefreshTime();
91+
cache.put(configurationKey, existingItem);
92+
}
93+
return existingItem;
94+
} catch (final RuntimeException e) {
95+
final ConfigurationCacheItem<GetConfigurationResponse> item = new ConfigurationCacheItem<>(cacheItemTtl);
96+
item.setException(e);
97+
98+
if (existingItem == null || existingItem.getValue() == null
99+
|| (item.getValue() != null
100+
&& !existingItem.getValue().configurationVersion().equals(item.getValue().configurationVersion()))) {
101+
item.calculateAndSetRefreshTime();
102+
cache.put(configurationKey, item);
103+
return item;
104+
} else {
105+
106+
existingItem.calculateAndSetRefreshTime();
107+
cache.put(configurationKey, existingItem);
108+
}
109+
return existingItem;
110+
}
111+
112+
}
113+
114+
115+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.amazonaws.samples.appconfig.cache;
2+
3+
import com.amazonaws.samples.appconfig.model.ConfigurationKey;
4+
import software.amazon.awssdk.services.appconfig.model.GetConfigurationResponse;
5+
6+
import java.util.Map;
7+
import java.util.Set;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
10+
public class ConfigurationCache {
11+
private final ConcurrentHashMap<ConfigurationKey, ConfigurationCacheItem<GetConfigurationResponse>> cache
12+
= new ConcurrentHashMap<>();
13+
14+
public ConfigurationCacheItem<GetConfigurationResponse> get(final ConfigurationKey key) {
15+
return cache.get(key);
16+
}
17+
18+
public void put(final ConfigurationKey key, final ConfigurationCacheItem<GetConfigurationResponse> value) {
19+
cache.put(key, value);
20+
}
21+
22+
public Set<Map.Entry<ConfigurationKey, ConfigurationCacheItem<GetConfigurationResponse>>> entrySet() {
23+
return cache.entrySet();
24+
}
25+
}

0 commit comments

Comments
 (0)