POST

djmil 2023-07-21 12:39:40 +02:00
parent 3195fee599
commit fd911cbc98
6 changed files with 369 additions and 205 deletions

@ -25,7 +25,19 @@
"state": {
"type": "markdown",
"state": {
"file": "Database.md",
"file": "Home.md",
"mode": "source",
"source": false
}
}
},
{
"id": "92c92033d31ed01c",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Home.md",
"mode": "source",
"source": false
}
@ -44,7 +56,7 @@
}
}
],
"currentTab": 1
"currentTab": 2
}
],
"direction": "vertical"
@ -110,7 +122,7 @@
"state": {
"type": "backlink",
"state": {
"file": "Database.md",
"file": "Home.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@ -127,7 +139,7 @@
"state": {
"type": "outgoing-link",
"state": {
"file": "Database.md",
"file": "Home.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
@ -150,7 +162,7 @@
"state": {
"type": "outline",
"state": {
"file": "Database.md"
"file": "Home.md"
}
}
}
@ -171,14 +183,17 @@
"command-palette:Open command palette": false
}
},
"active": "7e5ec70badaa86f3",
"active": "92c92033d31ed01c",
"lastOpenFiles": [
"Post.md",
"GET.md",
"Home.md",
"Get.md",
"assets/Pasted image 20230721100304.png",
"Database.md",
"IntegrationTests.md",
"UnitTests.md",
"Database.md",
"Home.md",
"assets/Pasted image 20230719152007.png",
"GET.md",
"Pasted image 20230719102301.png",
"assets/Pasted image 20230719102322.png",
"assets",

53
GET.md

@ -1,53 +0,0 @@
`src/main/java/djmil/cashcard/CashCardController.java`
```java
package example.cashcard;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CashCardController {
@GetMapping("/cashcards/{requestedId}")
public ResponseEntity<CashCard> findById(@PathVariable Long requestedId) {
if (requestedId.equals(99L)) {
CashCard cashCard = new CashCard(99L, 123.45);
return ResponseEntity.ok(cashCard);
} else {
return ResponseEntity.notFound().build();
}
}
}
```
# @GetMapping
Needs the URI Path and tells Spring to route `GET` requests strictly to the `findById` method.
## Alternative variant for providing URI mapping for `@RestConttroller`
```java
@RestController
@RequestMapping("/cashcards")
public class CashCardController {
@GetMapping("/{requestedId}")
public ResponseEntity<String> findById() {
```
# @PathVariable
Spring needs to know how to get the value of the `requestedId` parameter. This is done using the `@PathVariable` annotation. The fact that the parameter name matches the `{requestedId}` text *(URI Path)* within the `@GetMapping` parameter allows Spring to assign (inject) the correct value to the `requestedId` variable.
# ResponseEntity
REST says that the Response needs to contain a Cash Card in its body, and a Response code of 200 (OK). Spring Web provides the `ResponseEntity` class for this purpose. It also provides several utility methods to produce Response Entities. Here `ResponseEntity` used to create a Response with code **200 (OK)**, and a body containing a `CashCard`.
# Testing
Testing `GET` and other REST request is considerate to be [[IntegrationTests]].
Also, after running the application, you can use browser (or any other web-testing tool) by visiting http://localhost:8080/cashcards/99

89
Home.md

@ -1,4 +1,4 @@
This repo is my first attempt to learn `SpringBoot` following [this](https://spring.academy/courses/building-a-rest-api-with-spring-boot/lessons/introduction) tutorial. The setup is Visual Code IDE alongside with [SpringBoot](https://code.visualstudio.com/docs/java/java-spring-boot) plugin. It is also advised to read this wiki via [Obsidian](https://obsidian.md/)
This repo is my first attempt to learn `SpringBoot` following [this](https://spring.academy/courses/building-a-rest-api-with-spring-boot/lessons/introduction) tutorial. The setup is Visual Code IDE alongside with [SpringBoot](https://code.visualstudio.com/docs/java/java-spring-boot) plugin.
# Spring Initializr
@ -42,6 +42,8 @@ An End-to-End Test exercises the system using the same interface that a user wou
# RESTful API
REST is not exactly a standard; its merely a way to use HTTP to perform data operations. REST contains a number of guidelines, which developers shall follow to create coherent web-application.
In a RESTful system, data objects are called Resource Representations. The purpose of a RESTful API is to manage the state of these Resources. The chart below shows details about RESTful CRUD operations of an application.
|Operation|API Endpoint|HTTP Method|Response Status|
@ -71,11 +73,11 @@ public class CashCardController {
}
```
Thats all it takes to tell Spring: “create a REST Controller”. The Controller gets injected into Spring Web, which routes API requests (handled by the Controller) with help of [[GET#@GetMapping]] annotation to the correct method.
Thats all it takes to tell Spring: “create a REST Controller”. The Controller gets injected into Spring Web, which routes API requests (handled by the Controller) with help of [[Get#@GetMapping]] annotation to the correct method.
## Get
In [[GET]] requests, the body is empty. So, the request to read the Cash Card with an id of 123 would be:
****
In [[Get]] requests, the body is empty. So, the request to read the Cash Card with an id of 123 would be:
```
Request:
@ -96,6 +98,83 @@ Response:
}
```
## Put
Our REST API can now [[Get]] Cash Cards with a specific ID. Now it's time to add the Create endpoint to the API. Four questions well need to answer while doing this are:
1. Who specifies the ID - the client, or the server?
Here well choose to let the server create the ID. Why? Because its the simplest solution, and databases are efficient at managing unique IDs. However, for completeness, lets discuss our alternatives:
- We could require the client to provide the ID. This might make sense if there were a pre-existing unique ID, but thats not the case.
- We could allow the client to provide the ID optionally (and create it on the server if the client does not supply it). However, we dont have a requirement to do this, and it would complicate our application. If you think you might want to do this “just in case”, the [**Yagni**](https://martinfowler.com/bliki/Yagni.html) article might dissuade you.
2. In the API Request, how do we represent the object to be created?
3. Which HTTP method should we use in the Request?
4. What does the API send as a Response?
### Idempotence and HTTP
An **idempotent** operation is defined as one which, if performed more than once, results in the same outcome. In a REST API, an idempotent operation is one that even if it were to be performed several times, the resulting data on the server would be the same as if it had been performed only once.
For each method, the HTTP standard specifies whether it is idempotent or not. `GET`, `PUT`, and `DELETE` are idempotent, whereas `POST` and `PATCH` are not.
Since weve decided that the server will create IDs for every Create operation, the Create operation in our API **is NOT idempotent.** Since the server will create a new ID (on every Create request), if you call Create twice - even with the same content - youll end up with two different objects with the same content, but with different IDs. To summarize: every Create request will generate a new ID, thus no idempotency.
![[Pasted image 20230721100304.png]]
This leaves us with the `POST` and `PATCH` options. As it turns out, REST permits `POST` as one of the proper methods to use for Create operations, so we'll use it. Well revisit `PATCH` in a later lesson.
### The [[Post]] Request
The `POST` method allows a Body, so we will use the Body to send a JSON representation of the object:
Request:
- Method: `POST`
- URI: `/cashcards/`
- Body:
```json
{
amount: 123.45
}
```
In contrast, if you recall from a previous lesson, the `GET` operation includes the ID of the Cash Card in the URI and but _not_ in the request Body.
So why is there no ID in the Request? Because we decided to allow the server to create the ID. Thus, the data contract for the Read operation _is different_ from that of the Create operation.
### The Response
On successful creation, what HTTP Response Status Code should be sent? We could use `200 OK` (the response that Read returns), but theres a more specific, more accurate code for REST APIs: `201 CREATED`.
The fact that `CREATED` is the name of the code makes it seem intuitively appropriate, but theres another, more technical reason to use it: A response code of `200 OK` does _not_ answer the question “Was there any change to the server data?”. By returning the `201 CREATED` status, the API is specifically communicating that data was added to the data store on the server.
In addition to Status Code and a Body, HTTP Response also contains **Headers**. Headers have a name and a value. The HTTP standard specifies that the `Location` header in a `201 CREATED` response should contain the URI of the created resource. This is handy because it allows the caller to easily fetch the new resource using the GET endpoint (the one we implemented prior).
Here is the complete Response:
- Status Code: `201 CREATED`
- Header: `Location=/cashcards/42`
### Spring Web Convenience Methods
Spring Web provides methods which are geared towards the recommended use of HTTP and REST. For example, well use the `ResponseEntity.created(uriOfCashCard)` method to create the above response. This method requires you to specify the location, ensures the Location URI is well-formed (by using the `URI` class), adds the `Location` header, and sets the Status Code for you. And by doing so, this saves us from using more verbose methods. For example, the following two code snippets are equivalent (as long as `uriOfCashCard` is not `null`):
```java
return ResponseEntity
.created(uriOfCashCard)
.build();
```
Versus:
```java
return ResponseEntity
.status(HttpStatus.CREATED)
.header(HttpHeaders.LOCATION, uriOfCashCard.toASCIIString())
.build();
```
Arent you glad Spring Web provides the `.created()` convenience method?
# Database
The [**Separation of Concerns**](https://en.wikipedia.org/wiki/Separation_of_concerns) principle states that well-designed software should be modular, with each module having distinct and separate concerns from any other module.
@ -112,7 +191,7 @@ The Repository is the interface between the application and the database, and pr
## Choosing a Database
For our [[Database]] selection, well use an **embedded, in-memory** database. “Embedded” simply means that its a Java library, so it can be added to the project just like any other dependency. “In-memory” means that it stores data in memory only, as opposed to persisting data permanent, durable storage.
For our database selection, well use an **embedded, in-memory** database. “Embedded” simply means that its a Java library, so it can be added to the project just like any other dependency. “In-memory” means that it stores data in memory only, as opposed to persisting data permanent, durable storage.
The specific in-memory database well use is [H2](https://www.h2database.com/html/main.html). Fortunately, H2 is highly compatible with other relational databases, so dev-prod parity *(application might behave differently when running the in-memory database than when running in production)* wont be a big issue. Well use H2 for **convenience for local development**, but want to recognize the tradeoffs.

@ -69,7 +69,7 @@ void shouldReturnACashCardWhenDataIsSaved() {
Here we use `restTemplate` to make an HTTP `GET` request to the application endpoint `/cashcards/99`.
`restTemplate` will return a [[GET#ResponseEntity]], which we've captured in a variable we've named `response`.
`restTemplate` will return a [[Get#ResponseEntity]], which we've captured in a variable we've named `response`.
## Additional validators

123
Post.md Normal file

@ -0,0 +1,123 @@
# Test the HTTP POST Endpoint
We want our Cash Card API to behave as semantically correctly as possible. Meaning, users of our API should not be surprised by how it behaves. We'll begin by writing a failing test of what we expect success to look like.
Let's refer to the official Request for Comments for HTTP Semantics and Content ([RFC 7231](https://www.rfc-editor.org/rfc/rfc7231)) for guidance as to how our API should behave. For our `POST` endpoint, review this section about [HTTP POST](https://www.rfc-editor.org/rfc/rfc7231#section-4.3.3); note that we have added emphasis:
> If one or more resources has been created on the origin server as a result of successfully processing a POST request, **_the origin server SHOULD send a 201 (Created) response containing a Location header field that provides an identifier for the primary resource created ..._**
Edit `src/test/java/example/cashcard/CashCardApplicationTests.java` and add the following test method.
```java
import java.net.URI;
...
@Test
void shouldCreateANewCashCard() {
// The database will create and manage all unique `CashCard.id` values for us. We should not provide one.
CashCard newCashCard = new CashCard(null, 250.00);
// Post request MUST provide newCashCard data
// We expect nothing (Void) in the Post.Response.Body
ResponseEntity<Void> createResponse = restTemplate.postForEntity("/cashcards", newCashCard, Void.class );
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.CREATED);
// Get Location Header of newly created respurce and use it for Get request
URI locationOfNewCashCard = createResponse.getHeaders().getLocation();
ResponseEntity<String> getResponse = restTemplate.getForEntity(locationOfNewCashCard, String.class);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
// Validate Get response - a JSON for newly created CashCard entity
DocumentContext documentContext = JsonPath.parse(getResponse.getBody());
Number id = documentContext.read("$.id");
Double amount = documentContext.read("$.amount");
assertThat(id).isNotNull();
assertThat(amount).isEqualTo(250.00);
}
```
## `Location` Header
The official spec continue to state the following:
> send a 201 (Created) response **_containing a Location header field_** that provides an identifier for the primary resource created ...
In other words, when a `POST` request results in the successful creation of a resource, such as a new `CashCard`, the response should include information for how to retrieve that resource. We'll do this by supplying a URI in a [Response Header](https://www.rfc-editor.org/rfc/rfc7231#section-7) named "Location".
```java
URI locationOfNewCashCard = createResponse.getHeaders().getLocation();
```
Note that URI is indeed the correct entity here and _not_ a URL; a [URL is a type of URI](https://www.w3.org/TR/uri-clarification/#contemporary), while a URI is more generic.
# The POST
## Simple endpoint stub
The `POST` endpoint is similar to the `GET` endpoint in our `CashCardController`, but uses the `@PostMapping` annotation from Spring Web. The `POST` endpoint must accept the data we are submitting for our new `CashCard`, specifically the `amount`. But what happens if we don't accept the `CashCard`?
Edit `src/main/java/example/cashcard/CashCardController.java` and add the following method.
```java
import org.springframework.web.bind.annotation.PostMapping;
...
@PostMapping
private ResponseEntity createCashCard() {
return null;
}
```
Note that by returning nothing at all, Spring Web will automatically generate an HTTP Response Status code of `200 OK`. But, this isn't very satisfying -- our `POST` endpoint does nothing! So let's make our tests better.
## Actual implementation
```java
import org.springframework.web.util.UriComponentsBuilder;
...
@PostMapping
private ResponseEntity<Void> createCashCard(@RequestBody CashCard newCashCardRequest, UriComponentsBuilder ucb) {
// CrudRepository.save - Create
CashCard savedCashCard = cashCardRepository.save(newCashCardRequest);
URI locationOfNewCashCard = ucb
.path("cashcards/{id}")
.buildAndExpand(savedCashCard.id())
.toUri();
return ResponseEntity.created(locationOfNewCashCard).build();
}
```
### CrudRepository.save
```java
CashCard savedCashCard = cashCardRepository.save(newCashCardRequest);
```
`CrudRepository` provides methods that support creating, reading, updating, and deleting data from a data store. `cashCardRepository.save(newCashCardRequest)` does just as it says: it saves a new `CashCard` for us, and returns the saved object with a unique `id` provided by the database. Amazing!
### Post request, get the body
```java
createCashCard(@RequestBody CashCard newCashCardRequest, ...)
```
Unlike the `GET` we added earlier, the `POST` expects a request "body". This contains the data submitted to the API. Spring Web will deserialize the data into a `CashCard` for us.
### Location Header
```java
URI locationOfNewCashCard = ucb
.path("cashcards/{id}")
.buildAndExpand(savedCashCard.id())
.toUri();
```
This is constructing a URI to the newly created `CashCard`. This is the URI that the caller can then use to GET the newly-created `CashCard`. Note that `savedCashCard.id` is used as the identifier, which matches the `GET` endpoint's specification of `cashcards/<CashCard.id>`.
> Where did `UriComponentsBuilder` come from?
We were able to add `UriComponentsBuilder ucb` as a method argument to this `POST` handler method and it was automatically passed in. How so? It was injected from our now-familiar friend, Spring's IoC Container. Thanks, Spring Web!

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB