Remote Associations
Remote associations are Jimmer's first thoughts on microservice architecture.
Through vertical splitting, entity types can be divided into different microservices, or in other words, divided into different databases. The associations (including one-to-one, many-to-one, one-to-many and many-to-many) between entity types belonging to different microservices are remote associations.
Jimmer automatically implements automatic query of remote associated data.
That is, if the object fetcher contains remote association properties of any depth, it will query different parts of the data across multiple microservices, and finally merge the query results of different parts into the data structure expected by the object fetcher as a whole return. No need for developers to implement remote queries and data splicing themselves.
Databases
In this article, each entity is divided as follows:
-
BOOK_AUTHOR_MAPPING
as the many-to-many association intermediate table, can be divided into either thebook-service
service or theauthor-service
service. In this example, it is divided into thebook-service
service, the reason will be explained later. -
Solid arrows indicate foreign key references within the same database, whose validity can be freely chosen by developers.
-
Dashed arrows indicate foreign key references across database boundaries, which must be pseudo foreign keys.
To simplify the discussion, the database initialization scripts for the three services are as follows:
Language | Example | Service (Subproject) | Database Initialization SQL |
---|---|---|---|
Java | jimmer-examples/java/jimmer-cloud | store-service | jimmer-cloud/store-service/src/main/resources/store.sql |
book-service | jimmer-cloud/book-service/src/main/resources/book.sql | ||
author-service | jimmer-cloud/author-service/src/main/resources/author.sql | ||
Kotlin | jimmer-examples/kotlin/jimmer-cloud-kt | store-service | jimmer-cloud-kt/store-service/src/main/resources/store.sql |
book-service | jimmer-cloud-kt/book-service/src/main/resources/book.sql | ||
author-service | jimmer-cloud-kt/author-service/src/main/resources/author.sql |
The accompanying examples jimmer-examples/java/jimmer-cloud or jimmer-examples/kotlin/jimmer-cloud-kt use H2, each service will automatically initialize its own database after startup.
Entity Definition
In a separate subproject (the accompanying examples are jimmer-examples/java/jimmer-cloud/model or jimmer-examples/kotlin/jimmer-cloud-kt/model), define all entity types.
These entity types constitute the global model. Entity types belong to different microservices. Entities between different microservices constitute remote associations, which can actually be understood as the interaction contracts between entities belonging to different microservices.
- Java
- Kotlin
@Entity(microServiceName = "store-service")
public interface BookStore {
@OneToMany(mappedBy = "store")
List<Book> books();
...other code omitted...
}
@Entity(microServiceName = "store-service")
interface BookStore {
@OneToMany(mappedBy = "store")
val books: List<Book>
...other code omitted...
}
- Java
- Kotlin
@Entity(microServiceName = "book-service")
public interface Book {
@Nullable
@ManyToOne
BookStore store();
@ManyToMany
List<Author> authors();
...other code omitted...
}
@Entity(microServiceName = "book-service")
interface Book {
@ManyToOne
val store: BookStore?
@ManyToMany
val authors: List<Author>
...other code omitted...
}
- Java
- Kotlin
@Entity(microServiceName = "author-service")
public interface Book {
@ManyToMany(mappedBy = "authors")
List<Book> books();
...other code omitted...
}
@Entity(microServiceName = "author-service")
interface Author {
@ManyToMany(mappedBy = "authors")
val books: List<Book>
...other code omitted...
}
-
The
microServiceName
of the@Entity
annotation of the entity class is specified, so that it is clear which microservice each entity belongs to, i.e. which database.In this example, the entity types
BookStore
,Book
andAuthor
belong to the servicesstore-service
,book-service
andauthor-service
respectively.Here the microservice is the name of each service in the registry center, for spring-cloud it is the global configuration
spring.application.name
. -
The associations between entities belonging to different microservices are remote associations.
-
BookStore.books
andBook.store
:BookStore
belongs tostore-service
, whileBook
belongs tobook-service
-
Book.authors
andAuthor.books
:Book
belongs tobook-service
, whileAuthor
belongs toauthor-service
-
-
The many-to-one association
Book.store
must be nullable, because the remote association means its foreign key must reference data in another database, that is, it must be a pseudo foreign key. And in Jimmer, the field corresponding to a pseudo foreign key must be nullable.Please refer to Real and Pseudo Foreign Keys.
-
For the bidirectional many-to-many association between books and authors,
Book.authors
is the owning side, whileAuthor.books
is the inverse side (mappedBy
of@ManyToMany
is configured).infoJimmer stipulates that the intermediate table of remote associations must belong to the microservice where the owning side entity belongs.
Here the question left in the previous section is answered, why in this example the intermediate table
BOOK_AUTHOR_MAPPING
is defined in thebook-service
database instead of theauthor-service
database.
Registry Center
Microservice applications need a registry center, which is a basic feature of microservice architecture, no explanation is needed in this article.
In the accompanying examples, it is jimmer-examples/java/jimmer-cloud/registry-center or jimmer-examples/kotlin/jimmer-cloud-kt/registry-center
Implementing Services
Since the implementations of the microservices store-service
, book-service
and author-service
are highly similar, this document only explains how to implement store-service
.
Although this document only explains store-service
, to experience the effect of remote associations, all three services need to be fully implemented.
Complete code can be found in the accompanying examples jimmer-examples/java/jimmer-cloud or jimmer-examples/kotlin/jimmer-cloud-kt.
Enable Microservice Support in Jimmer
Any microservice must enable Jimmer's microservice support. There are two ways:
-
Use Spring Boot Starter
Modify spring configuration file
application.yml
(orapplication.properties
)spring:
application:
name: store-service
jimmer:
micro-service-name: ${spring.application.name}
...other configurations omitted...
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7000/eureka/Here, "store-service" is used both as the name of the current service in spring-cloud registry center and as the microservice configuration in Jimmer.
-
When using low-level APIs
- Java
- Kotlin
JSqlClient sqlClient = JSqlClient
.newBuilder()
.setMicroServiceName("...")
...other configurations omitted...
.build();val sqlClient = newKSqlClient {
setMicroServiceName("...")
...other configurations omitted...
}
Once jimmer.micro-service-name
is configured, Jimmer knows which entity types belong to the current service (here is BookStore
).
-
Using Jimmer's ORM APIs to operate on the entity types belonging to the current microservice will execute correctly, just like everything explained in previous documents.
-
Using Jimmer's ORM APIs to operate on entity types belonging to other microservices will throw exceptions.
As long as each service ensures spring.application.name
and jimmer.micro-service-name
are the same, Jimmer will automatically implement automatic query of remote associated data without requiring developers to write any code.
Implement Business Query Logic
- Java
- Kotlin
@GetMapping("/store/{id}/detail")
public @FetchBy("COMPLEX_FETCHER") BookStore findStoreDetail(
@PathVariable("id") long id
) {
return storeRepository.findNullable(id, COMPLEX_FETCHER);
}
public static final Fetcher<BookStore> COMPLEX_FETCHER =
Fetchers.BOOK_STORE_FETCHER ❶
.allScalarFields()
.books( ❷
Fetchers.BOOK_FETCHER
.allScalarFields()
.authors( ❸
Fetchers.AUTHOR_FETCHER
.allScalarFields()
)
);
@GetMapping("/store/{id}/detail")
fun findStoreDetail(
@PathVariable("id") id: Long
): @FetchBy("COMPLEX_FETCHER") BookStore? =
storeRepository.findNullable(id, COMPLEX_FETCHER)
companion object {
val COMPLEX_FETCHER =
newFetcher(BookStore::class).by { ❶
allScalarFields()
books { ❷
allScalarFields()
authors { ❸
allScalarFields()
}
}
}
}
-
❶ Query root aggregate, queried directly from the database belonging to the current service
store-service
-
❷ Query associated objects through remote association property
BookStore.books
Access remote microservice
book-service
indirectly to get data via HTTP protocol -
❸ Query associated objects through remote association property
Book.authors
Access remote microservice
author-service
indirectly to get data via HTTP protocol
Users only need to implement queries for root aggregate objects, queries for associated properties (including remote associations) are fully automated by Jimmer.
Therefore, even without writing any Java/Kotlin code for the other two microservices book-service
and author-service
, as long as their configuration files are modified to support microservices, this method can still run correctly.
Run Effects
Assuming store-service
is started on localhost with port 7001, accessing http://localhost:7001/store/1/detail
will get the result:
{
"id":1,
"name":"O'REILLY",
"website":null,
"books":[
{
"id":1,
"name":"Learning GraphQL",
"edition":1,
"price":50,
"authors":[
{
"id":1,
"firstName":"Eve",
"lastName":"Procello",
"gender":"FEMALE"
},
{
"id":2,
"firstName":"Alex",
"lastName":"Banks",
"gender":"MALE"
}
]
},
{
"id":2,
...omitted...
},
{
"id":3,
...omitted...
},
{
"id":4,
"name":"Effective TypeScript",
"edition":1,
"price":73,
"authors":[
{
"id":3,
"firstName":"Dan",
"lastName":"Vanderkam",
"gender":"MALE"
}
]
},
{
"id":5,
...omitted...
},
{
"id":6,
...omitted...
},
{
"id":7,
"name":"Programming TypeScript",
"edition":1,
"price":47.5,
"authors":[
{
"id":4,
"firstName":"Boris",
"lastName":"Cherny",
"gender":"MALE"
}
]
},
{
"id":8,
...omitted...
},
{
"id":9,
...omitted...
}
]
}