-
Notifications
You must be signed in to change notification settings - Fork 92
Add movies-rest #678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add movies-rest #678
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b4eed43
Add movies-rest
grego952 c3c5ed8
Add movies-rest
grego952 bc3eebe
Fix links
grego952 8092bde
Update MovieController
grego952 f8b62c9
Update docs
grego952 a3df0bb
Update id
grego952 0798a12
Merge branch 'main' into add-movies-rest
grego952 62b9c29
Add missing tutorial links
grego952 d8c7579
Merge branch 'main' into add-movies-rest
grego952 bfc89fd
Drop S
grego952 b1354e8
Merge branch 'add-movies-rest' of https://github.com/grego952/kyma-ru…
grego952 d412bd8
Apply suggestions from code review
grego952 783cd3f
Add note
grego952 ff3a6bc
Merge branch 'add-movies-rest' of https://github.com/grego952/kyma-ru…
grego952 17f0e94
Apply Gaurav suggestions
grego952 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| BPL_JVM_THREAD_COUNT=20 | ||
| JAVA_TOOL_OPTIONS=-XX:ReservedCodeCacheSize=40M -XX:MaxMetaspaceSize=80M -Xss512k |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Deploy a Spring Boot Movies REST API in SAP BTP, Kyma Runtime | ||
|
|
||
| ## Overview | ||
|
|
||
| > [!NOTE] | ||
| > This sample is used in the Fast Prototyping in SAP BTP, Kyma Runtime Using App Push tutorial. | ||
|
|
||
| This sample provides a Spring Boot REST API that manages movie records stored as JSON objects in an S3-compatible **SAP Object Store** service. | ||
|
|
||
| This sample demonstrates how to perform the following operations: | ||
|
|
||
| - Go from source code to a running, externally accessible application on Kyma runtime in a single command | ||
| - Iterate quickly on a prototype without writing Kubernetes manifests, Dockerfiles, or configuring a container registry | ||
| - Evolve a local prototype into an automated GitHub Actions CD pipeline | ||
|
|
||
| ## Architecture | ||
|
|
||
| ``` | ||
| GitHub Actions (CI/CD) | ||
| │ | ||
| ▼ | ||
| Kyma Runtime (Kubernetes) | ||
| ├── movies-rest Pod (Spring Boot, port 8080) | ||
| │ └── Istio sidecar (mTLS + ingress) | ||
| └── SAP Service Operator | ||
| └── ObjectStore ServiceInstance → S3 bucket | ||
| ``` | ||
|
|
||
| Each movie is stored as a JSON file at `movies/<id>.json` inside the bound S3 bucket. | ||
|
|
||
| > [!NOTE] | ||
| > Object Store is used here for the sake of simplicity. For applications with structured, relational use a proper database such as Hana Cloud or PostgreSQL. | ||
|
|
||
| ## Tech Stack | ||
|
|
||
| | Layer | Technology | | ||
| |---|---| | ||
| | Language | Java 21 | | ||
| | Framework | Spring Boot 3.3 | | ||
| | API docs | springdoc-openapi / Swagger UI | | ||
| | Storage | AWS SDK v2 → SAP Object Store (S3-compatible) | | ||
| | Service binding | `java-sap-service-operator` (SAP Cloud Service Binding) | | ||
| | Runtime | SAP BTP Kyma (Kubernetes + Istio) | | ||
| | CI/CD | GitHub Actions + `kyma-project/setup-kyma-cli` | | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| | Method | Path | Description | | ||
| |---|---|---| | ||
| | `GET` | `/movies` | List all movies | | ||
| | `GET` | `/movies/{id}` | Get a movie by ID | | ||
| | `POST` | `/movies` | Create a new movie (ID auto-generated) | | ||
| | `PUT` | `/movies/{id}` | Update an existing movie | | ||
| | `DELETE` | `/movies/{id}` | Delete a movie | | ||
|
|
||
| Interactive documentation is available at `/swagger-ui.html` after deployment. | ||
|
|
||
| ### Movie Resource | ||
|
|
||
| ```json | ||
| { | ||
| "id": "4e92e9c6-ebe3-4840-ae3c-2ede35ee4b74", | ||
| "title": "Blade Runner", | ||
| "year": 1982, | ||
| "director": "Ridley Scott", | ||
| "rating": 8.1 | ||
| } | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-parent</artifactId> | ||
| <version>3.3.0</version> | ||
| </parent> | ||
|
|
||
| <groupId>com.example</groupId> | ||
| <artifactId>movies</artifactId> | ||
| <version>1.0.0</version> | ||
|
|
||
| <properties> | ||
| <java.version>21</java.version> | ||
| </properties> | ||
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <dependency> | ||
| <groupId>com.sap.cloud.environment.servicebinding</groupId> | ||
| <artifactId>java-bom</artifactId> | ||
| <version>0.10.5</version> | ||
| <type>pom</type> | ||
| <scope>import</scope> | ||
| </dependency> | ||
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-web</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springdoc</groupId> | ||
| <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> | ||
| <version>2.5.0</version> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>com.sap.cloud.environment.servicebinding</groupId> | ||
| <artifactId>java-sap-service-operator</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>software.amazon.awssdk</groupId> | ||
| <artifactId>s3</artifactId> | ||
| <version>2.25.0</version> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-maven-plugin</artifactId> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> |
17 changes: 17 additions & 0 deletions
17
movies-rest/src/main/java/com/example/movies/Application.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.example.movies; | ||
|
|
||
| import io.swagger.v3.oas.annotations.OpenAPIDefinition; | ||
| import io.swagger.v3.oas.annotations.info.Info; | ||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
|
||
| @SpringBootApplication | ||
| @OpenAPIDefinition(info = @Info( | ||
| title = "Movies API", | ||
| version = "1.0.0", | ||
| description = "CRUD REST service for movies, backed by SAP BTP Object Store")) | ||
| public class Application { | ||
| public static void main(String[] args) { | ||
| SpringApplication.run(Application.class, args); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.example.movies; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| @Schema(description = "Movie resource") | ||
| public record Movie( | ||
| @Schema(description = "Auto-generated ID", example = "1714900000000", accessMode = Schema.AccessMode.READ_ONLY) | ||
| String id, | ||
| @Schema(description = "Movie title", example = "Blade Runner") | ||
| String title, | ||
| @Schema(description = "Release year", example = "1982") | ||
| int year, | ||
| @Schema(description = "Director name", example = "Ridley Scott") | ||
| String director, | ||
| @Schema(description = "Rating out of 10", example = "8.1") | ||
| Double rating) { | ||
| public Movie withId(String newId) { | ||
| return new Movie(newId, title, year, director, rating); | ||
| } | ||
| } |
102 changes: 102 additions & 0 deletions
102
movies-rest/src/main/java/com/example/movies/MovieController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| package com.example.movies; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.springframework.web.server.ResponseStatusException; | ||
| import software.amazon.awssdk.core.sync.RequestBody; | ||
| import software.amazon.awssdk.services.s3.S3Client; | ||
| import software.amazon.awssdk.services.s3.model.*; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/movies") | ||
| @Tag(name = "Movies", description = "CRUD operations for movie resources") | ||
| public class MovieController { | ||
|
|
||
| private final S3Client s3; | ||
| private final String bucket; | ||
| private final ObjectMapper mapper = new ObjectMapper(); | ||
|
|
||
| public MovieController(S3Client s3, String bucketName) { | ||
| this.s3 = s3; | ||
| this.bucket = bucketName; | ||
| } | ||
|
|
||
| @GetMapping | ||
| @Operation(summary = "List all movies") | ||
| public List<Movie> list() throws IOException { | ||
| ListObjectsV2Request request = ListObjectsV2Request.builder() | ||
| .bucket(bucket) | ||
| .prefix("movies/") | ||
| .build(); | ||
| ListObjectsV2Response response = s3.listObjectsV2(request); | ||
| return response.contents().stream() | ||
| .map(obj -> getMovie(obj.key())) | ||
| .toList(); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| @Operation(summary = "Get a movie by ID") | ||
| public Movie get(@PathVariable String id) { | ||
| return getMovie("movies/" + id + ".json"); | ||
| } | ||
|
|
||
| @PostMapping | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @Operation(summary = "Create a new movie") | ||
| public Movie create(@org.springframework.web.bind.annotation.RequestBody Movie movie) throws Exception { | ||
| Movie saved = movie.withId(UUID.randomUUID().toString()); | ||
| putMovie(saved); | ||
| return saved; | ||
| } | ||
|
|
||
| @PutMapping("/{id}") | ||
| @Operation(summary = "Update an existing movie") | ||
| public Movie update(@PathVariable String id, @org.springframework.web.bind.annotation.RequestBody Movie movie) throws Exception { | ||
| Movie saved = movie.withId(id); | ||
| putMovie(saved); | ||
| return saved; | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| @ResponseStatus(HttpStatus.NO_CONTENT) | ||
| @Operation(summary = "Delete a movie") | ||
| public void delete(@PathVariable String id) { | ||
| DeleteObjectRequest request = DeleteObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key("movies/" + id + ".json") | ||
| .build(); | ||
| s3.deleteObject(request); | ||
| } | ||
|
|
||
| private void putMovie(Movie movie) throws Exception { | ||
| byte[] json = mapper.writeValueAsBytes(movie); | ||
| PutObjectRequest request = PutObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key("movies/" + movie.id() + ".json") | ||
| .contentType("application/json") | ||
| .build(); | ||
| s3.putObject(request, software.amazon.awssdk.core.sync.RequestBody.fromBytes(json)); | ||
| } | ||
|
|
||
| private Movie getMovie(String key) { | ||
| try { | ||
| GetObjectRequest request = GetObjectRequest.builder() | ||
| .bucket(bucket) | ||
| .key(key) | ||
| .build(); | ||
| byte[] data = s3.getObject(request).readAllBytes(); | ||
| return mapper.readValue(data, Movie.class); | ||
| } catch (NoSuchKeyException e) { | ||
| throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Movie not found"); | ||
| } catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
| } |
49 changes: 49 additions & 0 deletions
49
movies-rest/src/main/java/com/example/movies/ObjectStoreConfig.java
|
grego952 marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.example.movies; | ||
|
|
||
| import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; | ||
| import com.sap.cloud.environment.servicebinding.api.ServiceBinding; | ||
| import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; | ||
| import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; | ||
| import software.amazon.awssdk.regions.Region; | ||
| import software.amazon.awssdk.services.s3.S3Client; | ||
|
|
||
| import java.net.URI; | ||
| import java.util.Map; | ||
|
|
||
| @Configuration | ||
| public class ObjectStoreConfig { | ||
|
|
||
| @Bean | ||
| public S3Client s3Client() { | ||
| ServiceBindingAccessor accessor = DefaultServiceBindingAccessor.getInstance(); | ||
|
|
||
| ServiceBinding binding = accessor.getServiceBindings().stream() | ||
| .filter(b -> "objectstore".equals(b.getServiceName().orElse(null))) | ||
| .findFirst() | ||
| .orElseThrow(() -> new IllegalStateException("No matching Object Store binding found")); | ||
|
|
||
| Map<String, Object> creds = binding.getCredentials(); | ||
|
|
||
| return S3Client.builder() | ||
| .region(Region.of((String) creds.get("region"))) | ||
| .endpointOverride(URI.create("https://" + creds.get("host"))) | ||
| .credentialsProvider(StaticCredentialsProvider.create( | ||
| AwsBasicCredentials.create( | ||
| (String) creds.get("access_key_id"), | ||
| (String) creds.get("secret_access_key")))) | ||
| .build(); | ||
| } | ||
|
|
||
| @Bean | ||
| public String bucketName() { | ||
| ServiceBindingAccessor accessor = DefaultServiceBindingAccessor.getInstance(); | ||
| ServiceBinding binding = accessor.getServiceBindings().stream() | ||
| .filter(b -> "objectstore".equals(b.getServiceName().orElse(null))) | ||
| .findFirst() | ||
| .orElseThrow(); | ||
| return (String) binding.getCredentials().get("bucket"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| server.port=8080 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.