Skip to content

Commit 7f1e75f

Browse files
author
AncaGhenade
committed
moar fixes
1 parent 462db53 commit 7f1e75f

File tree

11 files changed

+608
-440
lines changed

11 files changed

+608
-440
lines changed

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Shipment List Demo Application - AWS in PROD and LocalStack on DEV environment
2+
3+
### Prerequisites
4+
- Maven 3.8.5 & Java 17
5+
- AWS free tier account
6+
- Docker - for running LocalStack
7+
- AWS Command Line Interface - for managing your services
8+
- npm - for running the frontend app
9+
10+
## Purpose
11+
12+
This application was conceived for demonstration purposes to highlight the ease of switching from
13+
using actual AWS dependencies to having them emulated on LocalStack for your *developer environment*.
14+
Of course this comes with other advantages, but the first focus point is making the transition.
15+
16+
## What it does
17+
18+
*shipment-list-demo* is a Spring Boot application dealing with CRUD operations an employee can execute
19+
on a bunch of shipments that they're allowed to view - think of it like the Post app.
20+
The demo consists of a backend and a frontend implementation, using React to display the information.
21+
The AWS services involved are:
22+
- S3 for storing pictures
23+
- DynamoDB for the entities
24+
- Lambda function that will validate the pictures.
25+
26+
## How it works
27+
28+
![Diagram](app_diagram.png)
29+
30+
## How we will be using it
31+
We’ll be walking through a few scenarios using the application, and we expect it to maintain the
32+
behavior in both production and development environments.
33+
We’ll take advantage of one of the core features of the Spring framework that allows us to bind our
34+
beans to different profiles, such as dev, test, and prod. Of course, these beans need to know how to
35+
behave in each environment, so they’ll get that information from their designated configuration files,
36+
`application-prod.yml`, and `application-dev.yml`.
37+
38+
## Running it
39+
40+
### Production simulation
41+
42+
Now we don’t have a real production environment because that’s not the point here, but most likely,
43+
an application like this runs on a container orchestration platform, and all the necessary configs
44+
are still provided. Since we’re only simulating a production instance, all the configurations are
45+
kept in the `application-prod.yml` file.
46+
47+
Before getting started, it's important to note that an IAM user, who's credentials will be used,
48+
needs to be created with the following policies:
49+
- AmazonS3FullAccess
50+
- AWSLambda_FullAccess
51+
- AmazonDynamoDBFullAccess
52+
53+
The `scripts/new-bucket.sh` script will create the necessary S3 resource.
54+
55+
At startup @dynamobee helps set up the table we need and populate it with some sample data.
56+
@dynamobee is library for tracking, managing, and applying database changes
57+
The changelog acts as a database version control. It tracks all the changes made to the database,
58+
and helps you manage database migration.
59+
60+
To run the backend simply use
61+
```
62+
mvn spring-boot:run -Dspring-boot.run.profiles=prod
63+
```
64+
65+
Now `cd` into `src/main/shipment-list-frontend` and run `npm install` and `npm start`.
66+
This will spin up the React app that can be accessed on `localhost:3000`.
67+
68+
You should now be able to see a list of shipments with standard icons, that means that only the database
69+
is populated, the pictures still need to be added from the `sample-pictures` folder.
70+
The weight of a shipment we can perceive, but not the size, that's why we need to display it,
71+
using the "banana for scale" measuring unit. How else would we know??
72+
73+
The Lambda function is still not up. This falls under the `shipment-list-lambda-validator` project.
74+
```
75+
git clone https://github.com/tinyg210/shipment-list-lambda-validator.git
76+
```
77+
78+
The `create-lambda.sh` script will do everything that needs for the creation and configuration of the
79+
Lambda. (I know what you're thinking, Terraform will follow.)
80+
Run `add-notif-config-for-lambda.sh`, but before that remember to edit `notification-config.json` with
81+
your own AWS account ID. This will enable the Lambda to receive notifications every time a picture is being
82+
added to S3.
83+
84+
You should now be able to add a new picture for each shipment. Files that are not pictures will be deleted
85+
and the shipment picture will be replaced with a generic icon.
86+
87+
### Developer environment
88+
89+
To switch to using LocalStack instead of AWS services just run `docker compose up` to spin up a Localstack
90+
container.
91+
After that, the Spring Boot application needs to start using the dev profile:
92+
```
93+
mvn spring-boot:run -Dspring-boot.run.profiles=dev
94+
```
95+
96+
This should again populate the DynamoDB, this time on LocalStack.
97+
From here on, the rest of the steps are the same, but all the scripts that need to run end in `-local`,
98+
as they use the `awslocal` CLI.
99+
100+
The same actions should be easily achieved again, but locally.
101+
102+

docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ services:
66
container_name: localstack
77
ports:
88
- "4566:4566" # port of to where localstack can be addressed to
9-
- "9000:9000"
109
environment:
1110
- SERVICES=s3,dynamodb # a list of desired services you want to use.
1211
- DEFAULT_REGION=eu-central-1 # where the localstack mocks to be running

src/main/java/dev/ancaghenade/shipmentlistdemo/controller/ShipmentController.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.List;
77
import org.springframework.http.MediaType;
88
import org.springframework.web.bind.annotation.CrossOrigin;
9+
import org.springframework.web.bind.annotation.DeleteMapping;
910
import org.springframework.web.bind.annotation.GetMapping;
1011
import org.springframework.web.bind.annotation.PathVariable;
1112
import org.springframework.web.bind.annotation.PostMapping;
@@ -30,6 +31,11 @@ public List<Shipment> getAllShipments() {
3031
return shipmentService.getAllShipments();
3132
}
3233

34+
@DeleteMapping("/{shipmentId}")
35+
public String deleteShipment(@PathVariable("shipmentId") String shipmentId) {
36+
return shipmentService.deleteShipment(shipmentId);
37+
}
38+
3339
@PostMapping(
3440
path = "{shipmentId}/image/upload",
3541
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
@@ -43,6 +49,6 @@ public void uploadShipmentImage(@PathVariable("shipmentId") String shipmentId,
4349
@GetMapping(
4450
path = "{shipmentId}/image/download", produces = MediaType.IMAGE_JPEG_VALUE)
4551
public byte[] downloadShipmentImage(@PathVariable("shipmentId") String shipmentId) {
46-
return shipmentService.downloadShipmentImage(shipmentId);
52+
return shipmentService.downloadShipmentImage(shipmentId);
4753
}
4854
}

src/main/java/dev/ancaghenade/shipmentlistdemo/service/ShipmentService.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import dev.ancaghenade.shipmentlistdemo.entity.Shipment;
88
import dev.ancaghenade.shipmentlistdemo.repository.S3StorageService;
99
import dev.ancaghenade.shipmentlistdemo.repository.ShipmentRepository;
10+
import java.io.File;
1011
import java.io.IOException;
12+
import java.nio.file.Files;
1113
import java.util.HashMap;
1214
import java.util.List;
1315
import java.util.Map;
@@ -35,6 +37,10 @@ public List<Shipment> getAllShipments() {
3537
return shipmentRepository.getAllShipments();
3638
}
3739

40+
public String deleteShipment(String shipmentId) {
41+
return shipmentRepository.delete(shipmentId);
42+
}
43+
3844
public Shipment saveShipment(Shipment shipment) {
3945
return shipmentRepository.upsert(shipment);
4046
}
@@ -69,9 +75,13 @@ public byte[] downloadShipmentImage(String shipmentId) {
6975

7076
String path = format("%s/%s", BucketName.SHIPMENT_PICTURE.getBucketName(),
7177
shipment.getShipmentId());
72-
return Optional.ofNullable(shipment.getImageLink())
73-
.map(link -> s3StorageService.download(path, link))
74-
.orElse(new byte[0]);
78+
try {
79+
return Optional.ofNullable(shipment.getImageLink())
80+
.map(link -> s3StorageService.download(path, link))
81+
.orElse(Files.readAllBytes(new File("src/main/resources/placeholder.jpg").toPath()));
82+
} catch (IOException e) {
83+
throw new RuntimeException(e);
84+
}
7585
}
7686

7787

src/main/resources/application-dev.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
server:
2-
port: 8081
31

42
aws:
53
credentials:

src/main/resources/application-prod.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
server:
2-
port: 8081
3-
4-
servlet:
5-
multipart-max-file-size: 50MB
6-
7-
logging:
8-
level:
9-
root=debug:
101

112
aws:
123
credentials:

src/main/resources/application.yml

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ logging:
88
level:
99
root=debug:
1010

11-
aws:
12-
credentials:
13-
access-key: ${AWS_ACCESS_KEY_ID}
14-
secret-key: ${AWS_SECRET_ACCESS_KEY}
15-
dynamodb:
16-
endpoint: https://dynamodb.eu-central-1.amazonaws.com
17-
s3:
18-
endpoint: https://s3.eu-central-1.amazonaws.com
19-
region: eu-central-1
11+
spring:
12+
profiles:
13+
default: dev
Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,102 @@
11
[
2-
{
3-
"shipmentId": null,
4-
"recipient": {
5-
"name": "Home Sweet Home",
6-
"address": {
7-
"postalCode": "98653",
8-
"street": "47th Street",
9-
"number": "4",
10-
"city": "Springfield",
11-
"additionalInfo": null
12-
}
13-
},
14-
"sender": {
15-
"name": "Warehouse of Unicorns",
16-
"address": {
17-
"postalCode": "98653",
18-
"street": "47th Street",
19-
"number": "4",
20-
"city": "Townsville",
21-
"additionalInfo": null
22-
}
23-
},
24-
"weight": 2.3,
25-
"imageLink": null
2+
{
3+
"shipmentId": null,
4+
"recipient": {
5+
"name": "Home Sweet Home",
6+
"address": {
7+
"postalCode": "98653",
8+
"street": "47th Street",
9+
"number": "4",
10+
"city": "Springfield",
11+
"additionalInfo": null
12+
}
2613
},
27-
{
28-
"shipmentId": null,
29-
"recipient": {
30-
"name": "Buddy The Elf",
31-
"address": {
32-
"postalCode": "938746",
33-
"street": "Candy Cane Lane",
34-
"number": "1",
35-
"city": "North Pole",
36-
"additionalInfo": "Santa's Workshop"
37-
}
38-
},
39-
"sender": {
40-
"name": "The Grinch",
41-
"address": {
42-
"postalCode": "69869",
43-
"street": "Mount Crumpit",
44-
"number": "666",
45-
"city": "Whoville",
46-
"additionalInfo": "Cave"
47-
}
48-
},
49-
"weight": 0.0,
50-
"imageLink": null
14+
"sender": {
15+
"name": "Warehouse of Unicorns",
16+
"address": {
17+
"postalCode": "98653",
18+
"street": "47th Street",
19+
"number": "4",
20+
"city": "Townsville",
21+
"additionalInfo": null
22+
}
5123
},
52-
{
53-
"shipmentId": null,
54-
"recipient": {
55-
"name": "Walter White",
56-
"address": {
57-
"postalCode": "ALBQNM",
58-
"street": "Negra Arroyo Lane",
59-
"number": "308",
60-
"city": "Albuquerque",
61-
"additionalInfo": "RV storage"
62-
}
63-
},
64-
"sender": {
65-
"name": "Tony Stark",
66-
"address": {
67-
"postalCode": "NYCNY",
68-
"street": "Avenue of the Americas",
69-
"number": "64",
70-
"city": "New York City",
71-
"additionalInfo": "Stark Tower"
72-
}
73-
},
74-
"weight": 0.0,
75-
"imageLink": null
76-
}
24+
"weight": 2.3,
25+
"imageLink": null
26+
},
27+
{
28+
"shipmentId": null,
29+
"recipient": {
30+
"name": "Buddy The Elf",
31+
"address": {
32+
"postalCode": "938746",
33+
"street": "Candy Cane Lane",
34+
"number": "1",
35+
"city": "North Pole",
36+
"additionalInfo": "Santa's Workshop"
37+
}
38+
},
39+
"sender": {
40+
"name": "The Grinch",
41+
"address": {
42+
"postalCode": "69869",
43+
"street": "Mount Crumpit",
44+
"number": "666",
45+
"city": "Whoville",
46+
"additionalInfo": "Cave"
47+
}
48+
},
49+
"weight": 0.0,
50+
"imageLink": null
51+
},
52+
{
53+
"shipmentId": null,
54+
"recipient": {
55+
"name": "Walter White",
56+
"address": {
57+
"postalCode": "ALBQNM",
58+
"street": "Negra Arroyo Lane",
59+
"number": "308",
60+
"city": "Albuquerque",
61+
"additionalInfo": "RV storage"
62+
}
63+
},
64+
"sender": {
65+
"name": "Tony Stark",
66+
"address": {
67+
"postalCode": "NYCNY",
68+
"street": "Avenue of the Americas",
69+
"number": "64",
70+
"city": "New York City",
71+
"additionalInfo": "Stark Tower"
72+
}
73+
},
74+
"weight": 0.0,
75+
"imageLink": null
76+
},
77+
{
78+
"shipmentId": null,
79+
"recipient": {
80+
"name": "Harry Potter",
81+
"address": {
82+
"postalCode": "LNDNGB",
83+
"street": "Privet Drive",
84+
"number": "4",
85+
"city": "Little Whinging",
86+
"additionalInfo": ""
87+
}
88+
},
89+
"sender": {
90+
"name": "Hermione Granger",
91+
"address": {
92+
"postalCode": "OXFGB",
93+
"street": "Grimmauld Place",
94+
"number": "12",
95+
"city": "London",
96+
"additionalInfo": ""
97+
}
98+
},
99+
"weight": 1.0,
100+
"imageLink": null
101+
}
77102
]

0 commit comments

Comments
 (0)