Fork me on GitHub

Introduction

This documentation contains some help to examples from spring-examples repository is contains some spring / spring-boot playground projects

1. spring-boot under the hood

1.1. build and run

bash gradlew -t bootRun

1.2. spring-boot POWER

out-of-the-box conditions
// check if bean is in spring factory:
@OnBeanCondition

// check if class is in classpath:
@OnClassCondition

// on thruthy evaluated express condition:
@OnExpressionCondition

// on expected java version
@OnJavaCondition

// on expected JNDI branch
@OnJndiCondition

// check if property exists
@OnPropertyCondition

// check if resource exists
@OnResourceCondition

// check if WebApplicationContext exists
@OnWebApplicationCondition
additional conditions for combine or merge them together
// AND condition
@AllNestedConditions

// OR condition
@AnyNestedConditions

// NOT condition
@NoneNestedConditions

1.3. own starters

  1. create a starter configuration with provided by you custom functionality. In our case it’s kind of Java analog for JSON.stringify / JSON.parse from JS.

  2. create src/main/resources/META-INF/spring.factories file

src/main/resources/META-INF/spring.factories

TODO: finish readme…​

2. server-side events / sse emitter

2.1. test

open 2 browser windows and start chatting

or use console
curl localhost:8080/subscriptions # terminal 1
http --timeout 2629746000 --stream :8080/subscriptions # terminal 2
http post :8080/subscriptions/broadcast message="trololo" # send message to all subscribers
http delete :8080/subscriptions/2017-09-22-01-18-22-693 # unsubscribe by id

3. retry

3.1. test

bootstrap app
bash gradlew clean build bootRun
sucess response response
http :8080                                                                                             09:32:02
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Fri, 22 Sep 2017 06:32:04 GMT
Transfer-Encoding: chunked

{
    "message": {
        "attempts": 2,
        "createdAt": "2017-09-22T06:32:04.074+0000"
    }
}
bad response (recover strategy fallback response)
http :8080                                                                                            09:32:04
HTTP/1.1 400
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Fri, 22 Sep 2017 06:32:06 GMT
Transfer-Encoding: chunked

{
    "error": "fuck..."
}

links:

4. frontend web resources optimization

We can optimize resources in many different way. Here are 3:

  1. using Gradle plugin for NodeJS with common frontend npm flow

  2. using Wro4j Gradle Plugin

  3. using Gradle JS/CSS Plugins

4.1. test

bootstrap app
bash gradlew build
bash spring-boot-webpack/build/libs/spring-boot-webpack-0.0.1.jar --server.port=8000
bash spring-boot-wro4j/build/libs/spring-boot-wro4j-0.0.1.jar --server.port=8000
develoment
bash gradlew build

bash gradlew s-b-webpack:bRun
bash gradlew s-b-webpack:ui:yarn_watch

bash gradlew s-b-wro:bRun
bash gradles s-b-wro:processWebResources -t

bash gradlew s-b-g-j-c-p:bRun
bash gradles s-b-g-j-c-p:webResources -t
sucess response response
http :8080

links:

5. testing

TODO: In progress…​

testing
bash gradlew clean test

5.1. mokito

simple mokito
@RunWith(MockitoJUnitRunner.class)  /* 1 */
public class MokitoTest {

  @Mock
  RandomService randomService;      /* 2 */

  @InjectMocks
  UnstableResource sut;             /* 3 */

  @Test
  public void testMock() throws Exception {
    val res = sut.unstable();       /* 4 */
    assertThat(res, notNullValue());
  }
}
mokito testing
bash gradlew clean m:test

6. CQRS and Event-Sourcing

6.1. test

bootstrap
bash gradlew bootRun
get initial
http :8080/api/v1/order/items
[
    {
        "createdAt": "2017-09-30 09:08:23.160 +0000",
        "id": "cddca9831a0",
        "localDateTime": "2017-09-30 12:08:23.160 Z",
        "name": "two",
        "price": 4.82
    },
    {
        "createdAt": "2017-09-30 09:08:23.044 +0000",
        "id": "ef37433ecb6",
        "localDateTime": "2017-09-30 12:08:23.046 Z",
        "name": "one",
        "price": 4.21
    },
    {
        "createdAt": "2017-09-30 09:08:23.275 +0000",
        "id": "a6ad2f9fa48",
        "localDateTime": "2017-09-30 12:08:23.275 Z",
        "name": "three",
        "price": 6.4
    }
]
shopping
curl localhost:8080/api/v1/add-to-card -d'{"itemIds":["a6ad2f9fa48"]}' -H'content-type:application/json'
a6d3c929e35

## logs:
# 2017-09-30 12:09:00.952 : create: CreateOrderEvent(transactionId=a6d3c929e35, itemIds=[a6ad2f9fa48], createdAt=2017-09-30T12:09:00.950)
# 2017-09-30 12:09:00.955 : add: AddToCardEvent(transactionId=a6d3c929e35, itemId=a6ad2f9fa48, createdAt=2017-09-30T12:09:00.954)
# 2017-09-30 12:09:00.956 : store AddToCardEvent(transactionId=a6d3c929e35, itemId=a6ad2f9fa48, createdAt=2017-09-30T12:09:00.954)
# 2017-09-30 12:09:00.956 : store CreateOrderEvent(transactionId=a6d3c929e35, itemIds=[a6ad2f9fa48], createdAt=2017-09-30T12:09:00.950)

http post :8080/api/v1/add-to-card/a6d3c929e35 itemIds:='["ef37433ecb6"]'
a6d3c929e35

## logs:
# 2017-09-30 12:10:28.083 : create: CreateOrderEvent(transactionId=a6d3c929e35, itemIds=[ef37433ecb6], createdAt=2017-09-30T12:10:28.083)
# 2017-09-30 12:10:28.083 : add: AddToCardEvent(transactionId=a6d3c929e35, itemId=ef37433ecb6, createdAt=2017-09-30T12:10:28.083)
# 2017-09-30 12:10:28.083 : store AddToCardEvent(transactionId=a6d3c929e35, itemId=ef37433ecb6, createdAt=2017-09-30T12:10:28.083)
# 2017-09-30 12:10:28.083 : store CreateOrderEvent(transactionId=a6d3c929e35, itemIds=[ef37433ecb6], createdAt=2017-09-30T12:10:28.083)
verify order
curl localhost:8080/api/v1/order/a6d3c929e35 | jq
{
  "id": "a6d3c929e35",
  "itemIds": [
    "a6ad2f9fa48",
    "ef37433ecb6"
  ],
  "done": false,
  "createdAt": "2017-09-30 09:09:00.955 +0000",
  "localDateTime": "2017-09-30 12:09:00.955 Z"
}
publish order
http post :8080/api/v1/order/a6d3c929e35

## logs:
# 2017-09-30 12:11:20.081 : ship: ShipOrderEvent(transactionId=a6d3c929e35, createdAt=2017-09-30T12:11:20.081)
# 2017-09-30 12:11:20.083 : publish: ShopItem(id=a6ad2f9fa48, name=three, price=6.40, createdAt=2017-09-30T12:08:23.275+03:00[Europe/Kiev], localDateTime=2017-09-30T12:08:23.275)
# 2017-09-30 12:11:20.083 : publish: ShopItem(id=ef37433ecb6, name=one, price=4.21, createdAt=2017-09-30T12:08:23.044+03:00[Europe/Kiev], localDateTime=2017-09-30T12:08:23.046)
# 2017-09-30 12:11:20.083 : publish: ShipOrderEvent(transactionId=a6d3c929e35, createdAt=2017-09-30T12:11:20.081)
# 2017-09-30 12:11:20.083 : delivery: DeliveryOrderEvent(transactionId=a6d3c929e35, createdAt=2017-09-30T12:11:20.083)
# 2017-09-30 12:11:20.084 : store DeliveryOrderEvent(transactionId=a6d3c929e35, createdAt=2017-09-30T12:11:20.083)
# 2017-09-30 12:11:20.084 : store ShipOrderEvent(transactionId=a6d3c929e35, createdAt=2017-09-30T12:11:20.081)
verify order is completed (done)
curl localhost:8080/api/v1/order/a6d3c929e35 | jq
{
  "id": "a6d3c929e35",
  "itemIds": [
    "a6ad2f9fa48",
    "ef37433ecb6"
  ],
  "done": true,
  "createdAt": "2017-09-30 09:09:00.955 +0000",
  "localDateTime": "2017-09-30 12:09:00.955 Z"
}
check which goods are available
http :8080/api/v1/order/items
[
    {
        "createdAt": "2017-09-30 09:08:23.160 +0000",
        "id": "cddca9831a0",
        "localDateTime": "2017-09-30 12:08:23.160 Z",
        "name": "two",
        "price": 4.82
    }
]
eventstore
http :8080/api/v1/events
[
    {
        "createdAt": "2017093012090095043740950000000",
        "itemIds": [
            "a6ad2f9fa48"
        ],
        "transactionId": "a6d3c929e35",
        "type": "CreateOrderEvent"
    },
    {
        "createdAt": "2017093012090095443740954000000",
        "itemId": "a6ad2f9fa48",
        "transactionId": "a6d3c929e35",
        "type": "AddToCardEvent"
    },
    {
        "createdAt": "2017093012102808343828083000000",
        "itemId": "ef37433ecb6",
        "transactionId": "a6d3c929e35",
        "type": "AddToCardEvent"
    },
    {
        "createdAt": "2017093012102808343828083000000",
        "itemIds": [
            "ef37433ecb6"
        ],
        "transactionId": "a6d3c929e35",
        "type": "CreateOrderEvent"
    },
    {
        "createdAt": "2017093012112008143880081000000",
        "transactionId": "a6d3c929e35",
        "type": "ShipOrderEvent"
    },
    {
        "createdAt": "2017093012112008343880083000000",
        "transactionId": "a6d3c929e35",
        "type": "DeliveryOrderEvent"
    }
]

Spring Cloud Stream | CQRS and Event Sourcing

bash gradlew clean build
bash gradlew bootRun --parallel
open http://localhost:8001

Flow still buggy, but i’m too lazy to fix…​

create user command
http post :8080/

[
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3",
        "rel": "self"
    },
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/activate",
        "rel": "activate"
    }
]
get user query
http http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3

{
    "activated": false,
    "changes": [
        {
            "id": "47401f30-b3f1-4204-9ea4-bdb8c394d0b3"
        }
    ],
    "deactivated": false,
    "id": "47401f30-b3f1-4204-9ea4-bdb8c394d0b3",
    "nickname": "anonymous",
    "state": "INITIALIZED"
}
activate user command
http post http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/activate

[
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3",
        "rel": "self"
    },
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/change-nickname/newNickname",
        "rel": "change-nickname"
    }
]
change user nickname command
http post http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/change-nickname/max

[
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3",
        "rel": "self"
    },
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/deactivate",
        "rel": "deactivate"
    }
]
deactivate user events command
http post http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/deactivate

[
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3",
        "rel": "self"
    },
    {
        "href": "http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/replay",
        "rel": "replay"
    }
]
replay user events query
http http://localhost:8080/47401f30-b3f1-4204-9ea4-bdb8c394d0b3/replay

[
    {
        "UserInitializedEvent": {
            "id": "47401f30-b3f1-4204-9ea4-bdb8c394d0b3"
        }
    },
    {
        "UserActivatedEvent": {}
    },
    {
        "UserActivatedEvent": {}
    },
    {
        "NicknameChangedEvent": {
            "nickname": "max"
        }
    },
    {
        "UserActivatedEvent": {}
    },
    {
        "UserActivatedEvent": {}
    },
    {
        "NicknameChangedEvent": {
            "nickname": "max"
        }
    },
    {
        "UserDeactivatedEvent": {}
    }
]

resources:

7. Axon Framework

Axon banking app

7.1. old

start app, open 2 browser windows and start chatting
bash gradlew bootRun
open http://localhost:8001 # browser 1
open http://localhost:8001 # browser 2

Axon complaints app

bash gradlew bootRun
http :8080
http post :8080 company=first description="oh, no!"
http :8080/uuid...
run abd test
./mvnw clean package spring-boot:run

http post :8080/api/v1/orders
# output:
# Location: http://localhost:8080/api/v1/orders/$ID

http put :8080/api/v1/orders/$ID/add    k1=2 k3=4
http put :8080/api/v1/orders/$ID/add    k1=1 k3=1
http put :8080/api/v1/orders/$ID/remove k1=1 k3=2

http delete :8080/api/v1/orders/$ID
http post :8080/api/v1/orders
http post :8080/api/v1/orders

http :8080/order-query

FIXME: Failed with snapshots threshold…​

  1. MongoDB + Axon Framework App

  2. Maven / Gradle Kotlin configuration

build, run, test
./gradlew

java -jar axon-app/build/libs/*.jar
java -jar reactive-client/build/libs/*.jar --server.port=8000
java -jar es-client/build/libs/*.jar --server.port=8888

# create main entrance
http :8080/api/v1/entrance/register entranceId=main

# stream event client
#http --stream --timeout=123456 :8000
curl localhost:8000

# unlock main entrance
http put http://localhost:8080/api/v1/entrance/main/unlock

# create and unlock reception entrance
http :8080/api/v1/entrance/register entranceId=reception
http put http://localhost:8080/api/v1/entrance/reception/unlock

http :8080/api/v1/guest/register name=max
# ...
Location: http://localhost:8080/api/v1/guest/646fa336-dda6-4fdd-be38-05179ecd44e7/activate

http put http://localhost:8080/api/v1/guest/646fa336-dda6-4fdd-be38-05179ecd44e7/activate

# enter inside building / door
http post http://localhost:8080/api/v1/entrance/main/enter/646fa336-dda6-4fdd-be38-05179ecd44e7
http post http://localhost:8080/api/v1/entrance/reception/enter/646fa336-dda6-4fdd-be38-05179ecd44e7
http post http://localhost:8080/api/v1/entrance/reception/exit/646fa336-dda6-4fdd-be38-05179ecd44e7
http post http://localhost:8080/api/v1/entrance/main/exit/646fa336-dda6-4fdd-be38-05179ecd44e7

http :8888
http :8888 accept:application/json

http delete http://localhost:8080/api/v1/entrance/reception
http delete http://localhost:8080/api/v1/entrance/main
docker compose
./gradlew build composeUp
./gradlew composeDown

Axon / Spring-boot using Kotlin

build, run, test
./mvnw

java -jar target/*.jar

http post :8080/api/v1/counter\? counterId=c
http put  :8080/api/v1/counter/c/enable
http put  :8080/api/v1/counter/c/increment
http put  :8080/api/v1/counter/c/decrement\?amount=2

http :8080
http :8080\?collection=events
docker compose
./gradlew build composeUp
./gradlew composeDown

In fucking progress..

  1. axon

  2. spring-boot 1.x

  3. mongodb

  4. kotlin

  5. gradle

  6. maven

build, run, test
./mvnw # or
java -jar target/*.jar

./gradlew
java -jar build/libs/*.jar

http :8080/api/room
http :8080/api/room roomId=my-room
http :8080/api/room/my-room
http put :8080/api/room/my-room/max
http put :8080/api/room/my-room/valery
http :8080/api/member
http delete :8080/api/room/my-room/max
http delete :8080/api/room/my-room/valery
http :8080\?collection=events
docker compose
./gradlew build composeUp
./gradlew composeDown

links:

In fucking progress..

build, run, test
./mvnw

java -jar target/*.jar

# voting begin
http :8080/api/v1/registration id=my-vote name=my=vote
# or http :8080/api/v1/registration name=my=vote
# 201:
Location: http://localhost:8080/api/v1/registration/approve/my-vote
# ...
{
    "PUT": "http://localhost:8080/api/v1/registration/approve/my-vote",
    "message": "candidate has been registered. Please approve registration."
}

http put http://localhost:8080/api/v1/registration/approve/my-vote
# 202:
{
    "message": "Registration has been approved. Wait for elections begin and send your campaign URL to your electorate making vote for you!",
    "vote by electorId in request body - POST": "http://localhost:8080/api/v1/vote/my-vote"
}

http post http://localhost:8080/api/v1/vote/my-vote electorId=dag
http post http://localhost:8080/api/v1/vote/my-vote electorId=max
# 202, and logs: handling VotedForCandidateEvent(candidateId=my-vote, elector={"electorId": "max"})

http :8080
# output - current snapshot state...

# voting end

# counter begin
http post :8080/api/v1/counter\?counterId=my-counter
# or http post :8080/api/v1/counter
http put :8080//api/v1/counter/my-counter/enable
http post :8080//api/v1/counter/my-counter/increment
http post :8080//api/v1/counter/my-counter/increment
http :8080
# counter end

# show all events
http :8080\?collection=events

# show all snapshots
http :8080 # same as http :8080\?collection=snapshots

8. Jersey JAX-RS

bash gradlew bootRun

# rest
http :8080
http :8080/1

# global error handling
http :8080/4

# actuator
http :8080/application
http :8080/application/status
bash gradlew bootRun

# secured actuatoe endpoints
http --auth user:change-me :8080/applicaion/env
http --auth admin:change-me :8080/applicaion/env

# secured rest
http --auth user:change-me :8080/1
http --auth admin:change-me :8080/2

# check logs for
# ... user was here
# ... admin was here

9. Other projects

11. Enjoy! :)