Skip to content

Commit 5d41395

Browse files
authored
cbv4 initial source (#921)
Signed-off-by: Mark Nelson <mark.x.nelson@oracle.com>
1 parent 64aefe2 commit 5d41395

File tree

226 files changed

+7816
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

226 files changed

+7816
-0
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "docs-source/cloudbank/themes/hugo-theme-relearn"]
2+
path = docs-source/cloudbank/themes/hugo-theme-relearn
3+
url = https://github.com/McShelby/hugo-theme-relearn.git
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
+++
2+
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
3+
date = {{ .Date }}
4+
draft = true
5+
+++
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
+++
2+
archetype = "home"
3+
title = "CloudBank"
4+
+++
5+
6+
Welcome to CloudBank - an on-demand, self-paced learning resource you can use
7+
to learn about developing microservices with [Spring Boot](https://spring.io/projects/spring-boot)
8+
and deploying, running and managing them with [Oracle Backend for Spring Boot and Microservices](https://bit.ly/oraclespringboot).
9+
10+
You can follow through from beginning to end, or you can start at any module that you are interested in.
11+
12+
### What you will need
13+
14+
To complete the modules you will need `docker-compose` to run the backend and Oracle Database containers - you
15+
can use Docker Desktop, Rancher Desktop, Podman Desktop or similar.
16+
17+
You will need a Java SDK and either Maven or Gradle to build your applicaitons. An IDE is not strictly required,
18+
but you will have a better overall experience if you use one. We recommend Visual Studio Code or IntelliJ.
19+
20+
### Modules
21+
22+
CloudBank contains the following modules:
23+
24+
* **Module 1: Provision the Backend**
25+
This module guides you through provisioning an instance of the backend using
26+
Oracle Cloud Infrastructure (OCI) or on your local machine using Docker Compose.
27+
* **Module 2: Preparing your Development Environment**
28+
This module guides you through setting up your development environment including
29+
and IDE and a toolchain to build and test your applications.
30+
* **Module 3: Build the Account Microservice**
31+
This module walks you through building your very first microservice using Spring Boot.
32+
It assumes no prior knowledge of Spring Boot, so its a great place to start if you
33+
have not used Spring Boot before. This module demonstrates how to build a service
34+
with a *synchronous* API implmented as REST endpoints using Spring Web MVC, and how to
35+
store data in Oracle Database using Spring Data JPA.
36+
* **Module 4: Build the Check Processing Microservices**
37+
In this module, you will build microservices that use *asynchronous* messaging
38+
to communicate using Spring JMS and Oracle Transactional Event Queues. It introduces
39+
service discovery using Eureka Service Registry (part of [Spring Cloud Netflix](https://spring.io/projects/spring-cloud-netflix))
40+
and [Spring Cloud OpenFeign](https://spring.io/projects/spring-cloud-openfeign).
41+
* **Module 5: Manage Saga Transactions across Microservices**
42+
This module introduces the Saga pattern, a very important pattern that helps us
43+
manage data consistency across microservices. We will explore the Long Running
44+
Action specification, one implementation of the Saga pattern, and then build
45+
a Transfer microservice that will manage funds transfers using a saga.
46+
* **Module 6: Deploying the full CloudBank Application using the CLI**
47+
In this module, you will learn how to deploy the full CloudBank application
48+
to Oracle Backend for Spring Boot and Microservices using the CLI.
49+
If you prefer to use an IDE, skip this module and go to module 6 instead.
50+
* **Module 7: Deploying the full CloudBank Application using the IDE plugins**
51+
In this module, you will learn how to deploy the full CloudBank application
52+
to Oracle Backend for Spring Boot and Microservices using one of the
53+
IDE plugins - for Visual Studio Code or IntelliJ.
54+
* **Module 8: Explore the Backend Platform**
55+
This module will take you on a guided tour through the Oracle Backend for
56+
Spring Boot and Microservices platform. You will learn about the platform
57+
services and observability tools that are provided out-of-the=box
58+
* **Module 9: Cleanup**
59+
This module demonstrates how to clean up any resources created when
60+
you provisioned an instance of Oracle Backend for Spring Boot and Microservices
61+
on Oracle Cloud Infrastructure (OCI) or on your local machine using Docker Compose.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
+++
2+
archetype = "chapter"
3+
title = "Account Microservice"
4+
weight = 3
5+
+++
6+
7+
This is a new chapter.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
+++
2+
archetype = "page"
3+
title = "Query and Create Accounts"
4+
weight = 7
5+
+++
6+
7+
1. Create a service to list all accounts
8+
9+
Open your `AccountsController.java` file and add a final field in the class of type `AccountRepository`. And update the constructor to accept an argument of this type and set the field to that value. This tells Spring Boot to inject the JPA repository class we just created into this class. That will make it available to use in our services. The updated parts of your class should look like this:
10+
11+
```java
12+
import com.example.accounts.repository.AccountRepository;
13+
14+
// ...
15+
16+
final AccountRepository accountRepository;
17+
18+
public AccountController(AccountRepository accountRepository) {
19+
this.accountRepository = accountRepository;
20+
}
21+
```
22+
23+
Now, add a method to get all the accounts from the database and return them. This method should respond to the HTTP GET method. You can use the built-in `findAll` method on `JpaRepository` to get the data. Your new additions to your class should look like this:
24+
25+
```java
26+
import java.util.List;
27+
import com.example.accounts.model.Account;
28+
29+
// ...
30+
31+
@GetMapping("/accounts")
32+
public List<Account> getAllAccounts() {
33+
return accountRepository.findAll();
34+
}
35+
```
36+
37+
1. Rebuild and restart your application and test your new endpoint
38+
39+
If your application is still running, stop it with Ctrl+C (or equivalent) and then rebuild and restart it with this command:
40+
41+
```shell
42+
$ mvn spring-boot:run
43+
```
44+
45+
This time, when it starts up you will see some new log messages that were not there before. These tell you that it connected to the database successfully.
46+
47+
```text
48+
2023-02-25 15:58:16.852 INFO 29041 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
49+
2023-02-25 15:58:16.872 INFO 29041 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.15.Final
50+
2023-02-25 15:58:16.936 INFO 29041 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
51+
2023-02-25 15:58:17.658 INFO 29041 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.Oracle12cDialect
52+
2023-02-25 15:58:17.972 INFO 29041 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
53+
2023-02-25 15:58:17.977 INFO 29041 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
54+
```
55+
56+
Now you can test the new service with this command. It will not return any data as we haven't loaded any data yet.
57+
58+
```shell
59+
$ curl http://localhost:8080/api/v1/accounts
60+
HTTP/1.1 200
61+
Content-Type: application/json
62+
Transfer-Encoding: chunked
63+
Date: Sat, 25 Feb 2023 21:00:40 GMT
64+
65+
[]
66+
```
67+
68+
1. Add data to `ACCOUNTS` table
69+
70+
Notice that Spring Boot automatically set the `Content-Type` to `application/json` for us. The result is an empty JSON array `[]` as you might expect. Add some accounts to the database using these SQL statements (run these in your SQLcl terminal):
71+
72+
```sql
73+
insert into account.accounts (account_name,account_type,customer_id,account_other_details,account_balance)
74+
values ('Andy''s checking','CH','abcDe7ged','Account Info',-20);
75+
insert into account.accounts (account_name,account_type,customer_id,account_other_details,account_balance)
76+
values ('Mark''s CCard','CC','bkzLp8cozi','Mastercard account',1000);
77+
commit;
78+
```
79+
80+
1. Test the `/accounts` service
81+
82+
Now, test the service again. You may want to send the output to `jq` if you have it installed, so that it will be formatted for easier reading:
83+
84+
```shell
85+
$ curl -s http://localhost:8080/api/v1/accounts | jq .
86+
[
87+
{
88+
"accountId": 1,
89+
"accountName": "Andy's checking",
90+
"accountType": "CH",
91+
"accountCustomerId": "abcDe7ged",
92+
"accountOpenedDate": "2023-02-26T02:04:54.000+00:00",
93+
"accountOtherDetails": "Account Info",
94+
"accountBalance": -20
95+
},
96+
{
97+
"accountId": 2,
98+
"accountName": "Mark's CCard",
99+
"accountType": "CC",
100+
"accountCustomerId": "bkzLp8cozi",
101+
"accountOpenedDate": "2023-02-26T02:04:56.000+00:00",
102+
"accountOtherDetails": "Mastercard account",
103+
"accountBalance": 1000
104+
}
105+
]
106+
```
107+
108+
Now that you can query accounts, it is time to create an API endpoint to create an account.
109+
110+
1. Create an endpoint to create a new account.
111+
112+
Now we want to create an endpoint to create a new account. Open `AccountController.java` and add a new `createAccount` method. This method should return `ResponseEntity<Account>` this will allow you to return the account object, but also gives you access to set headers, status code and so on. The method needs to take an `Account` as an argument. Add the `RequestBody` annotation to the argument to tell Spring Boot that the input data will be in the HTTP request's body.
113+
114+
Inside the method, you should use the `saveAndFlush` method on the JPA Repository to save a new instance of `Account` in the database. The `saveAndFlush` method returns the created object. If the save was successful, return the created object and set the HTTP Status Code to 201 (Created). If there is an error, set the HTTP Status Code to 500 (Internal Server Error).
115+
116+
Here's what the new method (and imports) should look like:
117+
118+
```java
119+
120+
import java.net.URI;
121+
...
122+
...
123+
import org.springframework.web.bind.annotation.PostMapping;
124+
import org.springframework.web.bind.annotation.RequestBody;
125+
import org.springframework.http.HttpStatus;
126+
import org.springframework.http.ResponseEntity;
127+
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
128+
129+
// ...
130+
131+
@PostMapping("/account")
132+
public ResponseEntity<Account> createAccount(@RequestBody Account account) {
133+
try {
134+
Account newAccount = accountRepository.saveAndFlush(account);
135+
URI location = ServletUriComponentsBuilder
136+
.fromCurrentRequest()
137+
.path("/{id}")
138+
.buildAndExpand(newAccount.getAccountId())
139+
.toUri();
140+
return ResponseEntity.created(location).build();
141+
} catch (Exception e) {
142+
return new ResponseEntity<>(account, HttpStatus.INTERNAL_SERVER_ERROR);
143+
}
144+
}
145+
```
146+
147+
1. Test the `/account` endpoint
148+
149+
Rebuild and restart the application as you have previously. Then test the new endpoint. You will need to make an HTTP POST request, and you will need to set the `Content-Type` header to `application/json`. Pass the data in as JSON in the HTTP request body. Note that Spring Boot Web will handle mapping the JSON to the right fields in the type annotated with the `RequestBody` annotation. So a JSON field called `accountName` will map to the `accountName` field in the JSON, and so on.
150+
151+
Here is an example request and the expected output (yours will be slightly different):
152+
153+
```shell
154+
$ curl -i -X POST \
155+
-H 'Content-Type: application/json' \
156+
-d '{"accountName": "Dave", "accountType": "CH", "accountOtherDetail": "", "accountCustomerId": "abc123xyz"}' \
157+
http://localhost:8080/api/v1/account
158+
HTTP/1.1 201
159+
Location: http://localhost:8080/api/v1/account/3
160+
Content-Length: 0
161+
Date: Wed, 14 Feb 2024 21:33:17 GMT
162+
```
163+
164+
Notice the HTTP Status Code is 201 (Created). The service returns the URI for the account was created in the header.
165+
166+
1. Test endpoint `/account` with bad data
167+
168+
Now try a request with bad data that will not be able to be parsed and observe that the HTTP Status Code is 400 (Bad Request). If there happened to be an exception thrown during the `save()` method, you would get back a 500 (Internal Server Error):
169+
170+
```shell
171+
$ curl -i -X POST -H 'Content-Type: application/json' -d '{"bad": "data"}' http://localhost:8080/api/v1/account
172+
HTTP/1.1 400
173+
Content-Type: application/json
174+
Transfer-Encoding: chunked
175+
Date: Sat, 25 Feb 2023 22:05:24 GMT
176+
Connection: close
177+
178+
{"timestamp":"2023-02-25T22:05:24.350+00:00","status":400,"error":"Bad Request","path":"/api/v1/account"}
179+
```
180+
181+
1. Implement Get Account by Account ID endpoint
182+
183+
Add new method to your `AccountController.java` class that responds to the HTTP GET method. This method should accept the account ID as a path variable. To accept a path variable, you place the variable name in braces in the URL path in the `@GetMapping` annotation and then reference it in the method's arguments using the `@PathVariable` annotation. This will map it to the annotated method argument. If an account is found, you should return that account and set the HTTP Status Code to 200 (OK). If an account is not found, return an empty body and set the HTTP Status Code to 404 (Not Found).
184+
185+
Here is the code to implement this endpoint:
186+
187+
```java
188+
import org.springframework.http.HttpStatus;
189+
import org.springframework.http.ResponseEntity;
190+
import org.springframework.web.bind.annotation.PathVariable;
191+
import java.util.Optional;
192+
193+
// ...
194+
195+
@GetMapping("/account/{accountId}")
196+
public ResponseEntity<Account> getAccountById(@PathVariable("accountId") long accountId) {
197+
Optional<Account> accountData = accountRepository.findById(accountId);
198+
try {
199+
return accountData.map(account -> new ResponseEntity<>(account, HttpStatus.OK))
200+
.orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
201+
} catch (Exception e) {
202+
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
203+
}
204+
}
205+
```
206+
207+
1. Restart and test `/account/{accountId}` endpoint
208+
209+
Restart the application and test this new endpoint with this command (note that you created account with ID 2 earlier):
210+
211+
```shell
212+
$ curl -s http://localhost:8080/api/v1/account/2 | jq .
213+
{
214+
"accountId": 2,
215+
"accountName": "Mark's CCard",
216+
"accountType": "CC",
217+
"accountCustomerId": "bkzLp8cozi",
218+
"accountOpenedDate": "2023-02-26T02:04:56.000+00:00",
219+
"accountOtherDetails": "Mastercard account",
220+
"accountBalance": 1000
221+
}
222+
```
223+
224+
That completes the basic endpoints. In the next task, you can add some additional endpoints if you wish. If you prefer, you can skip that task because you have the option to deploy the fully pre-built service in a later module (Deploy the full CloudBank Application) if you choose.
225+

0 commit comments

Comments
 (0)