Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
babyfish-ct committed Apr 20, 2022
2 parents 3effeb7 + 5a99463 commit dac3fc3
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 57 deletions.
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,113 @@ Its development speed is very fast, and the usage method is very simple.
## Run the example
Use intellij the open the [example](https://github.com/babyfish-ct/graphql-provider/tree/main/example), after waiting for gradle to finish all tasks, start the program and visit http://localhost:8080/graphiql.

> The query "findBooks" is pagination query, so you must specify the argument "first" or "last". Otherwise, exeption will be thrown
In order to experience this example, there are three things to note

1. *Query.books* is pagination query, so you must specify the argument "first" or "last", like this
```
query {
books(first: 10) {
edges {
node {
name
}
}
}
}
```
Otherwise, exeption will be thrown
```
{
"errors": [
{
"message": "java.lang.IllegalArgumentException: neither 'first' nor 'last' is specified",
"locations": [],
"path": [
"books"
],
"extensions": {
"errorType": "INTERNAL"
}
}
],
"data": {
"books": null
}
}
```
2. *BookStore.avgPrice*, *Query.myFavoriteBooks*, *Mutation.like* and *Mutation.unlike* are not open to anonymous users
Look at this query
```
query {
books(first: 10) {
edges {
node {
name
store {
name
avgPrice # Access BookStore.avgPrice
}
}
}
}
}
```
*BookStore.avgPrice* is not open to anonymous users, spring security throws *AccessDeniedException* when *Authorization* of HTTP request header is not specified.
You can login
- by graphql way
```
query {
login(email: "[email protected]", password: "123456") {
accessToken
}
}
```
- or by rest way
http://localhost:8080/authentication/[email protected]&password=123456
No matter which way you use, you can get the *accessToken*. Copy the *accessToken* and make the following HTTP header for the originally rejected graphql request
````
{ "Authorization": "...paste AccessToken here..."}
````
3. *Mutation.saveBook*, *Mutation.saveBooks*, *Mutation.saveShallowTree* and *Mutation.saveBookDeepTree* are only open to administrators.
```
mutation {
saveBook(input: {
name: "NewBook",
price: 70
}) {
id
}
}
```
This mutation can only be executed by admininstor, spring security throws *AccessDeniedException* when the current user is not administrator.
You can login as administrator *([email protected])*
- by graphql way
```
query {
login(email: "[email protected]", password: "123456") {
accessToken
}
}
```
- or by rest way
http://localhost:8080/authentication/[email protected]&password=123456
No matter which way you use, you can get the *accessToken* of administrator. Copy the *accessToken* of administrator and make the following HTTP header for the originally rejected graphql request
````
{ "Authorization": "...paste AccessToken of administrator here..."}
````
## User guide & Documentation
These links are not only user guides, but also documentation.
Expand All @@ -35,6 +140,7 @@ Although the [example](https://github.com/babyfish-ct/graphql-provider/tree/main
7. [Pagination query](./doc/pagination.md)
8. [Map inputs](./doc/input-mapper.md)
9. [Execute Mutation](./doc/mutation.md)
10. Security(Will come soon)
-----------
## Other projects
Expand Down
12 changes: 5 additions & 7 deletions doc/association-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@ class BookMapper: EntityMapper<Book, UUID>() {

fun authors(firstName: String?, lastName: String?) = // α
runtime.filterList(Book::authors) { // β
firstName?.let {
db {
db {
firstName?.let {
where { table.firstName ilike it } // γ
}
}
lastName?.let {
db {
lastName?.let {
where { table.lastName ilike it }
}
}
Expand Down Expand Up @@ -66,7 +64,7 @@ class BookMapper: EntityMapper<Book, UUID>() {
Start app, access http://localhost:8080/graphiql, and execute
```
query {
findBooks {
books {
name
store {
name
Expand All @@ -82,7 +80,7 @@ The response is
```
{
"data": {
"findBooks": [
"books": [
{
"name": "Effective TypeScript",
"store": {
Expand Down
2 changes: 1 addition & 1 deletion doc/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Modify the "build.gradle.kts",
```
dependencies {
implementation("org.babyfish.graphql.provider:graphql-provider-starter-dgs:0.0.5")
implementation("org.babyfish.graphql.provider:graphql-provider-starter-dgs:0.0.7")
ksp("org.babyfish.kimmer:kimmer-ksp:0.3.1")
runtimeOnly("io.r2dbc:r2dbc-h2:0.8.5.RELEASE")
}
Expand Down
6 changes: 3 additions & 3 deletions doc/entity-mapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ import org.springframework.stereotype.Service
@Service // α
class BookQuery: Query() { // β
fun findBooks(): List<Book> =
suspend fun books(): List<Book> =
runtime.queryList {} // γ
}
```
Expand All @@ -257,7 +257,7 @@ Now, you can run it, start app and access http://localhost:8080/graphiql/
Execute
```
query {
findBooks {
books {
name
store {
name
Expand All @@ -273,7 +273,7 @@ The response is
```
{
"data": {
"findBooks": [
"books": [
{
"name": "Learning GraphQL",
"store": {
Expand Down
75 changes: 62 additions & 13 deletions doc/mutation.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,25 +219,21 @@ class BookMutation(
private val r2dbcClient: R2dbcClient
) : Mutation {

@Transactional
suspend fun saveBook(
input: ImplicitInput<Book, BookInputMapper>
): Book =
r2dbcClient.save(input.entity, input.saveOptionsBlock).entity()

@Transactional

suspend fun saveBooks(
inputs: ImplicitInputs<Book, BookInputMapper>
): List<Book> =
r2dbcClient.save(inputs.entities, inputs.saveOptionsBlock).entities()

@Transactional
suspend fun saveBookShallowTree(
input: ImplicitInput<Book, BookShallowTreeInputMapper>
): Book =
r2dbcClient.save(input.entity, input.saveOptionsBlock).entity()

@Transactional
suspend fun saveBookDeepTree(
input: ImplicitInput<Book, BookDeepTreeInputMapper>
): Book =
Expand Down Expand Up @@ -305,39 +301,92 @@ The client can easily access the id assigned to each object after the mutation i

## 3. Add transaction

The internal implementation of *R2dbcClient.save* will execute multiple SQLs, in order to ensure that the entire mutation is fully complete or completely undone, please use @*org.springframework.transaction.annotation.Transactional*
The internal implementation of *R2dbcClient.save* will execute multiple SQLs, in order to ensure that the entire mutation is fully complete or completely undone.

> After experimenting, the *@Transactional* annotation seems to have no effect on suspend functions. So you can use the DSL to complete the transaction configuration
There are two ways to use the transaction DSL

1. Class level
```kt
@Service
class MyMutation(): Mutation() {

override fun MutationDSL.config() {
transaction()
}

suspend fun field1(...argument...): ReturnType = runtime.mutate {
... async code here...
}

suspend fun field2(...argument...): ReturnType = runtime.mutate {
... async code here...
}
}
```
Once class-level configuration is used *(either transactions as discussed here, or security as explained in subsequent documentation)*, all functions need to be wrapped in "runtime.mutate"

2. Function level
```kt
@Service
class MyMutation(): Mutation() {

suspend fun field1(...argument...): ReturnType = runtime.mutateBy {
transaction()
async {
... async code here...
}
}

suspend fun field2(...argument...): ReturnType = runtime.mutateBy {
transaction()
async {
... async code here...
}
}
}
```

The two usages can be mixed, and the function-level configuration will override the class-level configuration

The final code is
In this example we use class level configuration, the final code is

```kt
@Service
class BookMutation(
private val r2dbcClient: R2dbcClient
) : Mutation {

@Transactional
override fun MutationDSL.config() {
transaction()
}

suspend fun saveBook(
input: ImplicitInput<Book, BookInputMapper>
): Book =
): Book = runtime.muate {
r2dbcClient.save(input.entity, input.saveOptionsBlock).entity()
}

@Transactional
suspend fun saveBooks(
inputs: ImplicitInputs<Book, BookInputMapper>
): List<Book> =
): List<Book> = runtime.muate {
r2dbcClient.save(inputs.entities, inputs.saveOptionsBlock).entities()
}

@Transactional
suspend fun saveBookShallowTree(
input: ImplicitInput<Book, BookShallowTreeInputMapper>
): Book =
): Book = runtime.muate {
r2dbcClient.save(input.entity, input.saveOptionsBlock).entity()
}

@Transactional
suspend fun saveBookDeepTree(
input: ImplicitInput<Book, BookDeepTreeInputMapper>
): Book =
): Book = runtime.muate {
r2dbcClient.save(input.entity, input.saveOptionsBlock).entity()
}
}
```

Expand Down
Loading

0 comments on commit dac3fc3

Please sign in to comment.