Skip to content
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

fix: add documentation for spanner template sample (native image support) #2457

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ include::cloudfoundry.adoc[]

include::kotlin.adoc[]

include::native.adoc[]

== Configuration properties

To see the list of all Google Cloud related configuration properties please check link:appendix.html[the Appendix page].
Expand Down
72 changes: 72 additions & 0 deletions docs/src/main/asciidoc/native.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
== Native Image Support (Preview)

https://www.graalvm.org/docs/[GraalVM Native Image] allows you to ahead-of-time compile Java code to a standalone executable.
GraalVM native images are supported since Spring Boot 3.0 and Spring Framework 6.0. Discover more in Spring Boot guide to https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html[GraalVM Native Image Support].

Refer to https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.introducing-graalvm-native-images.understanding-aot-processing[this section] to learn more about Spring ahead-of-time processing and its limitations. There are also additional limitations specific to Spring Cloud projects listed https://github.com/spring-cloud/spring-cloud-release/wiki/AOT-transformations-and-native-image-support[here].

WARNING: Native image support for Spring Framework on Google Cloud is in **Preview**.
Compatibility with native image compilation is available "as is" and has no SLAs or technical support commitments, as defined in our https://cloud.google.com/products#product-launch-stages[product launch stages] for previews.

=== Currently Included Modules

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-cloud-gcp-core[Google Cloud Core]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-sql[Cloud SQL]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-storage[Cloud Storage]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-pubsub[Cloud PubSub]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-integration[Spring Integration for Cloud PubSub and Cloud Storage]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-cloud-stream[Spring Cloud Stream binder to Google Cloud Pub/Sub]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-cloud-bus[Cloud Pub/Sub as the Spring Cloud Bus]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-trace[Cloud Trace]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-logging[Cloud Logging]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-monitoring[Cloud Monitoring]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-data-cloud-spanner[Spring Data Cloud Spanner] (See details in <<detail_guides>> )

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-data-cloud-datastore[Spring Data Cloud Datastore] (See details in <<detail_guides>> )

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#spring-data-cloud-firestore[Spring Data Cloud Firestore] (See details in <<detail_guides>> )

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#bigquery[BigQuery]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#cloud-vision[Cloud Vision]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#secret-manager[Cloud Secret Manager]

- https://googlecloudplatform.github.io/spring-cloud-gcp//reference/html/index.html#google-cloud-key-management-service[Cloud KMS]

=== Setup and Usage

Spring Boot includes buildpack support for native images directly for both Maven and Gradle.
You can also generate a native executable directly without using Docker with GraalVM Native Build Tools.

To build a native image container or generate a native executable using Maven you should ensure that your `pom.xml` file uses the `spring-boot-starter-parent` in order to inherit the `native` profile and the `org.graalvm.buildtools:native-maven-plugin`.

For details on setup and build, please refer to Spring Boot guide to https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#native-image.developing-your-first-application[Developing Your First GraalVM Native Application]

=== Module-specific Implementation Notes [[detail_guides]]

==== Datastore

To use `DatastoreTemplate` methods on Domain types not managed by `DatastoreRepository`, you need to register runtime hints for these domain POJOs in your application. For more details, refer to example
https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-data-datastore-basic-sample/src/main/java/com/example/DomainTypeRuntimeHints.java[`DomainTypeRuntimeHints`] in Datastore basic sample.

===== Relationships
@LazyReference is not yet supported for native image.

==== Firestore

To use `FirestoreTemplate` methods on Domain types not managed by `FirestoreReactiveRepository`, you need to register runtime hints for these domain POJOs in your application. For more details, refer to example https://github.com/GoogleCloudPlatform/spring-cloud-gcp/tree/main/spring-cloud-gcp-samples/spring-cloud-gcp-data-firestore-sample/src/main/java/com/example/DomainTypeRuntimeHints.java[`DomainTypeRuntimeHints`] in Reactive Firestore Repository sample application.

==== Spanner

To use `SpannerTemplate` methods on Domain types not managed by `SpannerRepository`, you need to register runtime hints for these domain POJOs in your application. For more details, refer to example https://github.com/GoogleCloudPlatform/spring-cloud-gcp/blob/main/spring-cloud-gcp-samples/spring-cloud-gcp-data-spanner-template-sample/src/main/java/com/example/SpannerTemplateRuntimeHints.java[`SpannerTemplateRuntimeHints`] in the Spanner Template sample application.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.springframework.data.annotation.Id;

/**
* This class represents a single computer stored in Datastore. To demonstrate template usage
* without repository.
*/
@Entity(name = "computer")
public class Computer {
@Id Long id;

private final String brand;

private final String model;

private final int year;

public Computer(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Computer computer)) {
return false;
}
return year == computer.year
&& Objects.equal(id, computer.id)
&& Objects.equal(brand, computer.brand)
&& Objects.equal(model, computer.model);
}

@Override
public int hashCode() {
return Objects.hashCode(id, brand, model, year);
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("brand", brand)
.add("model", model)
.add("year", year)
.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import com.google.cloud.spring.data.datastore.core.DatastoreTemplate;
import com.google.cloud.spring.data.datastore.repository.DatastoreRepository;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;

/**
* Demonstrates adding runtime hints for native compilation. Runtime hints needs to be registered
* for Data POJO classes when desire to use {@link DatastoreTemplate} methods directly without a
* repository class for that domain type extending {@link DatastoreRepository} class.
*/
public class DomainTypeRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {

hints
.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(Computer.class)),
hint ->
hint.withMembers(
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
Expand All @@ -43,6 +44,7 @@
classes = DatastoreBookshelfExample.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@EnabledIfSystemProperty(named = "it.datastore", matches = "true")
@ImportRuntimeHints(DomainTypeRuntimeHints.class)
class DatastoreBookshelfExampleIntegrationTests {

@Autowired private DatastoreTemplate datastoreTemplate;
Expand Down Expand Up @@ -143,6 +145,18 @@ void removeAllBooksTest() {
});
}

@Test
void templateMethodTest() {
// @ImportRuntimeHints(DomainTypeRuntimeHints.class) is needed for this test to run successfully
// with AOT. Computer is a domain class not managed by Spring Data Repository.
Computer chromebook = new Computer("Lenovo", "Chromebook Duet 5", 2023);

this.datastoreTemplate.deleteAll(Computer.class);
assertThat(this.datastoreTemplate.count(Computer.class)).isZero();
this.datastoreTemplate.save(chromebook);
assertThat(this.datastoreTemplate.findAll(Computer.class)).contains(chromebook);
}

private String sendRequest(String url, String json, HttpMethod method) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("Content-Type", "application/json");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import com.google.cloud.spring.data.firestore.FirestoreReactiveRepository;
import com.google.cloud.spring.data.firestore.FirestoreTemplate;
import java.util.Arrays;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;

/**
* Demonstrates adding runtime hints for native compilation. Runtime hints needs to be registered
* for Data POJO classes when desire to use {@link FirestoreTemplate} methods directly without a
* repository class for that domain type extending {@link FirestoreReactiveRepository} class.
*/
public class DomainTypeRuntimeHints implements RuntimeHintsRegistrar {

@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {

hints
.reflection()
.registerTypes(
Arrays.asList(TypeReference.of(Person.class)),
hint ->
hint.withMembers(
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS,
MemberCategory.DECLARED_FIELDS));

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import com.google.cloud.firestore.annotation.DocumentId;
import com.google.cloud.spring.data.firestore.Document;

/**
* Example POJO to demonstrate Spring Cloud GCP Spring Data Firestore operations.To demonstrate
* template usage without repository.
*/
@Document(collectionName = "people")
public class Person {

@DocumentId String name;

int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;

Expand All @@ -40,6 +41,7 @@
webEnvironment = WebEnvironment.RANDOM_PORT,
classes = FirestoreSampleApplication.class)
@TestPropertySource("classpath:application-test.properties")
@ImportRuntimeHints(DomainTypeRuntimeHints.class)
class FirestoreSampleApplicationIntegrationTests {
private static final User ALPHA_USER =
new User("Alpha", 49, singletonList(new Pet("rat", "Snowflake")));
Expand Down Expand Up @@ -97,4 +99,12 @@ void saveUserTest() {
phoneNumbers = testUserClient.listPhoneNumbers("Alpha");
assertThat(phoneNumbers).isEmpty();
}

@Test
void templateTest() {
this.firestoreTemplate.save(new Person("Alice", 12)).block();
assertThat(this.firestoreTemplate.count(Person.class).block()).isEqualTo(1);
this.firestoreTemplate.deleteAll(Person.class).block();
assertThat(this.firestoreTemplate.count(Person.class).block()).isEqualTo(0);
}
}