├── .gitignore ├── README.adoc ├── adoc.sh ├── build.sh ├── ex01 └── solution │ └── hello-galaxy │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── micronaut-cli.yml │ └── src │ ├── main │ ├── java │ │ └── hello │ │ │ └── galaxy │ │ │ ├── Application.java │ │ │ └── HelloController.java │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── hello │ └── galaxy │ └── HelloControllerTest.java ├── ex02 └── solution │ └── clubs │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── micronaut-cli.yml │ └── src │ ├── main │ ├── java │ │ └── clubs │ │ │ ├── Application.java │ │ │ ├── ClubsApi.java │ │ │ ├── client │ │ │ └── ClubsClient.java │ │ │ ├── controller │ │ │ └── ClubController.java │ │ │ ├── domain │ │ │ ├── Club.java │ │ │ ├── ClubRepository.java │ │ │ └── ClubRepositoryImpl.java │ │ │ └── init │ │ │ └── DataLoader.java │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── clubs │ ├── ClubControllerTest.java │ └── ClubRepositoryImplTest.java ├── ex03 └── solution │ └── fixtures │ ├── .gitignore │ ├── Dockerfile │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── micronaut-cli.yml │ └── src │ ├── main │ ├── java │ │ └── fixtures │ │ │ ├── Application.java │ │ │ ├── clubs │ │ │ ├── Club.java │ │ │ ├── ClubsApi.java │ │ │ └── ClubsClient.java │ │ │ ├── controller │ │ │ ├── FixtureController.java │ │ │ └── FixtureResponse.java │ │ │ ├── domain │ │ │ ├── Fixture.java │ │ │ ├── FixtureRepository.java │ │ │ └── FixtureRepositoryImpl.java │ │ │ ├── init │ │ │ └── DataLoader.java │ │ │ └── service │ │ │ └── FixtureService.java │ └── resources │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── fixtures │ ├── ClubsClientMock.java │ ├── FixtureClient.java │ ├── FixtureControllerTest.java │ ├── FixtureRepositoryImplTest.java │ └── FixtureServiceTest.java └── images └── consul.png /.gitignore: -------------------------------------------------------------------------------- 1 | .asciidoctor/ 2 | .gradle/ 3 | build/ 4 | .classpath 5 | .project 6 | .settings/ 7 | bin/ 8 | .idea/ 9 | *.iml 10 | out/ 11 | .vscode/ 12 | README.html 13 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = image:https://raw.githubusercontent.com/micronaut-projects/static-website/gh-pages/images/favicon-32x32.png[] https://alvarosanchez.github.io/micronaut-workshop-java/[Micronaut Workshop] 2 | Alvaro Sanchez-Mariscal 3 | :toc: left 4 | :toclevels: 4 5 | :source-highlighter: highlightjs 6 | :icons: font 7 | :imagesdir: ./images 8 | 9 | ++++ 10 | Fork me on GitHub 11 | ++++ 12 | 13 | Introductory workshop about http://micronaut.io[Micronaut]. 14 | 15 | == Software Requirements 16 | 17 | In order to do this workshop, you need the following: 18 | 19 | * Linux or MacOS with shell access, and the following installed: 20 | - `curl`. 21 | - `wget`. 22 | - `unzip`. 23 | - `git`. 24 | * JDK 8. 25 | * Docker. Please pull the following images before attending the workshop: 26 | - `consul`. 27 | - `mongo`. 28 | 29 | === Micronaut CLI 30 | 31 | 1. Install http://sdkman.io[SDKMAN!] if you haven't done so already. 32 | 2. Install Micronaut CLI: 33 | 34 | $ sdk install micronaut 35 | 36 | 3. Ensure the CLI is installed properly: 37 | 38 | $ mn --version 39 | | Micronaut Version: 1.1.0 40 | | JVM Version: 1.8.0_191 41 | 42 | === Clone this repository 43 | 44 | Once done, you can clone this repo: 45 | 46 | ---- 47 | git clone https://github.com/alvarosanchez/micronaut-workshop-java.git 48 | ---- 49 | 50 | NOTE: You will find each exercise's template files on each `exNN` folder. Solution is always inside a `solution` folder. To highlight the actions you actually need to perform, an icon is used: icon:hand-o-right[] 51 | 52 | == Application architecture 53 | 54 | Throughout this workshop, we will be creating a football (soccer) management system. 55 | 56 | ifdef::generate-diagrams[] 57 | [plantuml, football-diagram, png] 58 | .... 59 | together { 60 | node Fixtures 61 | database Mongo 62 | } 63 | 64 | together { 65 | node Clubs 66 | database H2 67 | } 68 | 69 | Clubs -> H2 70 | H2 -[hidden]- Fixtures 71 | Fixtures -> Mongo 72 | Fixtures -> Clubs 73 | .... 74 | endif::[] 75 | 76 | image::football-diagram.png[] 77 | 78 | * `clubs` is the microservice responsible for managing clubs. It uses Hibernate 79 | as a data access layer. 80 | * `fixtures` manages all game fixtures, storing its data in MongoDB. For the 81 | teams playing in a game, it doesn't store their full details, but rather their 82 | ID. It has a service-discovery-enabled HTTP client to fetch club details from 83 | the `clubs` microservice. 84 | 85 | :numbered: 86 | 87 | == Getting started with Micronaut and its CLI (25 minutes) 88 | 89 | TIP: Change to the `ex01` directory to work on this exercise 90 | 91 | The Micronaut CLI is the recommended way to create new Micronaut projects. The 92 | CLI includes commands for generating specific categories of projects, allowing 93 | you to choose between build tools, test frameworks, and even pick the language 94 | you wish to use in your application. The CLI also provides commands for generating 95 | artifacts such as controllers, client interfaces, and serverless functions. 96 | 97 | The `create-app` command is the starting point for creating Micronaut applications. 98 | The CLI is based on the concept of **profiles**. A profile consist of a project 99 | template (or skeleton), optional features, and profile-specific commands. Commands 100 | from a profile typically are specific to the profile application type; for example, 101 | the `service` profile (designed for creation of microservice applications) provides 102 | the `create-controller` and `create-client` commands. 103 | 104 | === Listing profiles (3 minutes) 105 | 106 | icon:hand-o-right[] You can list the available profiles with the `list-profiles` command: 107 | 108 | ---- 109 | $ mn list-profiles 110 | | Available Profiles 111 | -------------------- 112 | cli The cli profile 113 | configuration The profile for creating the configuration 114 | federation The federation profile 115 | function-aws The function profile for AWS Lambda 116 | function-aws-alexa The function profile for AWS Lambda 117 | grpc Profile for Creating GRPC Services 118 | kafka The Kafka messaging profile 119 | profile A profile for creating new Micronaut profiles 120 | rabbitmq The RabbitMQ messaging profile 121 | service The service profile 122 | ---- 123 | 124 | 125 | Applications generated from a profile can be personalised with **features**. A 126 | feature further customises the newly created project by adding additional 127 | dependencies to the build, more files to the project skeleton, etc. 128 | 129 | === Getting information about a profile (2 minutes) 130 | 131 | icon:hand-o-right[] To see all the features of a profile, you can 132 | use the `profile-info` command: 133 | 134 | +++
+++ 135 | Click to expand 136 | +++
+++ 137 | ---- 138 | $ mn profile-info service 139 | | Profile: service 140 | -------------------- 141 | The service profile 142 | 143 | | Provided Commands: 144 | -------------------- 145 | create-bean Creates a singleton bean 146 | create-client Creates a client interface 147 | create-controller Creates a controller and associated test 148 | create-job Creates a job with scheduled method 149 | create-test Creates a simple test for the project's testing framework 150 | create-websocket-client Creates a Websocket client 151 | create-websocket-server Creates a Websocket server 152 | help Prints help information for a specific command 153 | 154 | | Provided Features: 155 | -------------------- 156 | annotation-api Adds Java annotation API 157 | application Facilitates creating an executable JVM application and adds support for creating fat/uber JARs 158 | aws-api-gateway Adds support for AWS API Gateway 159 | aws-api-gateway-graal Creates an AWS API Gateway Proxy Lambda with Graal Native Image 160 | cassandra Adds support for Cassandra in the application 161 | config-consul Adds support for Distributed Configuration with Consul (https://www.consul.io) 162 | discovery-consul Adds support for Service Discovery with Consul (https://www.consul.io) 163 | discovery-eureka Adds support for Service Discovery with Eureka 164 | elasticsearch Adds support for Elasticsearch in the application 165 | file-watch Adds automatic restarts and file watch 166 | flyway Adds support for Flyway database migrations (https://flywaydb.org/) 167 | graal-native-image Allows Building a Native Image 168 | graphql Adds support for GraphQL in the application 169 | groovy Creates a Groovy application 170 | hibernate-gorm Adds support for GORM persistence framework 171 | hibernate-jpa Adds support for Hibernate/JPA 172 | http-client Adds support for creating HTTP clients 173 | http-server Adds support for running a Netty server 174 | java Creates a Java application 175 | jdbc-dbcp Configures SQL DataSource instances using Commons DBCP 176 | jdbc-hikari Configures SQL DataSource instances using Hikari Connection Pool 177 | jdbc-tomcat Configures SQL DataSource instances using Tomcat Connection Pool 178 | jib Adds support for Jib builds 179 | jrebel Adds support for class reloading with JRebel (requires separate JRebel installation) 180 | junit Adds support for the JUnit 5 testing framework 181 | kafka Adds support for Kafka 182 | kafka-streams Adds support for Kafka Streams 183 | kotlin Creates a Kotlin application 184 | liquibase Adds support for Liquibase database migrations (http://www.liquibase.org/) 185 | logback Adds Logback Logging 186 | management Adds support for management endpoints 187 | micrometer Adds support for Micrometer metrics 188 | micrometer-atlas Adds support for Micrometer metrics (w/ Atlas reporter) 189 | micrometer-cloudwatch Adds support for Micrometer metrics (w/ AWS Cloudwatch reporter) 190 | micrometer-graphite Adds support for Micrometer metrics (w/ Graphite reporter) 191 | micrometer-prometheus Adds support for Micrometer metrics (w/ Prometheus reporter) 192 | micrometer-statsd Adds support for Micrometer metrics (w/ Statsd reporter) 193 | mongo-gorm Configures GORM for MongoDB for Groovy applications 194 | mongo-reactive Adds support for the Mongo Reactive Streams Driver 195 | neo4j-bolt Adds support for the Neo4j Bolt Driver 196 | neo4j-gorm Configures GORM for Neo4j for Groovy applications 197 | netflix-archaius Adds support for Netflix Archaius in the application 198 | netflix-hystrix Adds support for Netflix Hystrix in the application 199 | netflix-ribbon Adds support for Netflix Ribbon in the application 200 | picocli Adds support for command line parsing (http://picocli.info) 201 | postgres-reactive Adds support for the Reactive Postgres driver in the application 202 | rabbitmq Adds support for RabbitMQ in the application 203 | redis-lettuce Configures the Lettuce driver for Redis 204 | security-jwt Adds support for JWT (JSON Web Token) based Authentication 205 | security-session Adds support for Session based Authentication 206 | spek Adds support for the Spek testing framework 207 | spock Adds support for the Spock testing framework 208 | springloaded Adds support for class reloading with Spring-Loaded 209 | swagger-groovy Configures Swagger (OpenAPI) Integration for Groovy 210 | swagger-java Configures Swagger (OpenAPI) Integration for Java 211 | swagger-kotlin Configures Swagger (OpenAPI) Integration for Kotlin 212 | tracing-jaeger Adds support for distributed tracing with Jaeger (https://www.jaegertracing.io) 213 | tracing-zipkin Adds support for distributed tracing with Zipkin (https://zipkin.io) 214 | ---- 215 | +++
+++ 216 | 217 | === Creating and running a _hello galaxy_ (15 minutes) 218 | 219 | As explained avobe, the `create-app` command can be used to create new projects. 220 | It accepts some flags: 221 | 222 | .Create-App Flags 223 | |=== 224 | |Flag|Description|Example 225 | 226 | |`build` 227 | |Build tool (one of `gradle`, `maven` - default is `gradle`) 228 | |`--build maven` 229 | 230 | |`profile` 231 | |Profile to use for the project (default is `service`) 232 | |`--profile function-aws` 233 | 234 | |`features` 235 | |Features to use for the project, comma-separated 236 | |`--features security-jwt,mongo-gorm` 237 | 238 | |`inplace` 239 | |If present, generates the project in the current directory (project name is optional if this flag is set) 240 | |`--inplace` 241 | |=== 242 | 243 | icon:hand-o-right[] Let's create a _hello galaxy_ project: 244 | 245 | ---- 246 | $ mn create-app hello-galaxy 247 | | Generating Java project... 248 | | Application created at /Users/alvarosanchez/hello-galaxy 249 | ---- 250 | 251 | icon:hand-o-right[] Now, move into the generated `hello-galaxy` folder and let's 252 | create a controller: 253 | 254 | ---- 255 | $ mn create-controller hello 256 | | Rendered template Controller.java to destination src/main/java/hello/galaxy/HelloController.java 257 | | Rendered template ControllerTest.java to destination src/test/java/hello/galaxy/HelloControllerTest.java 258 | ---- 259 | 260 | icon:hand-o-right[] Open the generated `HelloController.java` with your favourite 261 | IDE and make it return "Hello Galaxy!": 262 | 263 | [source,java] 264 | ---- 265 | include::./ex01/solution/hello-galaxy/src/main/java/hello/galaxy/HelloController.java[tag=action,indent=0] 266 | ---- 267 | 268 | icon:hand-o-right[] Now, run the application: 269 | 270 | $ ./gradlew run 271 | 272 | You will see a line similar to the following once the application has started 273 | 274 | 14:40:01.187 [main] INFO io.micronaut.runtime.Micronaut - Startup completed in 957ms. Server Running: http://localhost:8080 275 | 276 | icon:hand-o-right[] Then, on another shell, make a request to your service: 277 | 278 | ---- 279 | $ curl 0:8080/hello 280 | Hello Galaxy! 281 | ---- 282 | 283 | === Write an automated test (5 minutes) 284 | 285 | While testing manually is acceptable in some situations, going forward it is 286 | better to have automated tests to exercise our applications. Fortunately, 287 | Micronaut makes testing super easy! 288 | 289 | Micronaut applications can be tested with any testing framework, because 290 | `io.micronaut.context.ApplicationContext` is capable of spinning up embedded 291 | instances quite easily. The CLI adds support for using JUnit, Spock and Spek. 292 | 293 | In addition to that, if you are using JUnit 5 or Spock, there is special support that allows 294 | to remove most of the boilerplate about starting/stopping server and injecting beans. Check 295 | the https://micronaut-projects.github.io/micronaut-test/latest/guide/index.html[Micronaut Test] 296 | project for more information. 297 | 298 | We will use Gradle to run the tests, however, if you want to run them from your IDE, make 299 | sure you enable annotation processors. For example, in Intellij IDEA: 300 | 301 | image::http://guides.micronaut.io/micronaut-data-access-jpa-hibernate/img/annotationprocessorsintellij.png[] 302 | 303 | icon:hand-o-right[] Now, change the generated `src/test/java/hello/galaxy/HelloControllerTest.java` 304 | to look like this: 305 | 306 | +++
+++ 307 | Click to expand 308 | +++
+++ 309 | [source,java] 310 | ---- 311 | include::./ex01/solution/hello-galaxy/src/test/java/hello/galaxy/HelloControllerTest.java[] 312 | ---- 313 | +++
+++ 314 | 315 | icon:hand-o-right[] Then, run the tests: 316 | 317 | ./gradlew test 318 | 319 | Once finished, you should see an output similar to: 320 | 321 | BUILD SUCCESSFUL in 5s 322 | 323 | == Creating the Clubs microservice (70 minutes) 324 | 325 | TIP: Change to the `ex02` directory to work on this exercise. 326 | 327 | icon:hand-o-right[] In this exercise we are creating the `clubs` microservice. Start with: 328 | 329 | ---- 330 | mn create-app --features hibernate-jpa clubs 331 | ---- 332 | 333 | And open it in your IDE. 334 | 335 | The `hibernate-jpa` feature will bring to the newly created project: 336 | 337 | * The required build dependencies to have Hibernate, a Tomcat-based JDBC connection pool 338 | and an H2 in-memory database (`build.gradle`). 339 | * The data source configuration to use such H2 database (`src/main/resources/application.yml`). 340 | 341 | icon:hand-o-right[] Check yourself the above files to see how it is configured. 342 | 343 | === JPA layer (15 minutes) 344 | 345 | Our model will reside in the `clubs.domain` package. We need to configure JPA to search for 346 | entities in this package. 347 | 348 | icon:hand-o-right[] Change `jpa` section of `src/main/resources/application.yml` so that it looks like: 349 | 350 | [source,yaml] 351 | ---- 352 | include::./ex02/solution/clubs/src/main/resources/application.yml[tags=jpa] 353 | ---- 354 | 355 | icon:hand-o-right[] Let's define first a `Club` entity under 356 | `src/main/java/clubs/domain/Club.java` with 2 string attributes: 357 | `name` (mandatory) and `stadium` (optional). 358 | 359 | You need to use JPA annotations on the entity such as `@Entity` and `@Id`. If you are not familiar with JPA, 360 | check the solution file. 361 | 362 | icon:hand-o-right[] Next, define repository named `ClubRepository` as an interface with 363 | the following operations: 364 | 365 | [source,java] 366 | ---- 367 | include::./ex02/solution/clubs/src/main/java/clubs/domain/ClubRepository.java[tag=operations,indent=0] 368 | ---- 369 | 370 | Now, let's write the implementation using JPA: 371 | 372 | +++
+++ 373 | Click to expand 374 | +++
+++ 375 | [source,java] 376 | ---- 377 | include::./ex02/solution/clubs/src/main/java/clubs/domain/ClubRepositoryImpl.java[tag=class] 378 | ---- 379 | +++
+++ 380 | 381 | icon:hand-o-right[] Now, let's write a test for our implementation: 382 | 383 | +++
+++ 384 | Click to expand 385 | +++
+++ 386 | [source,java] 387 | ---- 388 | include::./ex02/solution/clubs/src/test/java/clubs/ClubRepositoryImplTest.java[tag=test] 389 | ---- 390 | +++
+++ 391 | 392 | WARNING: For package-scanning reasons, you need to ensure your test classes are located at 393 | the top of the package hierarchy, so that Micronaut can find annotations in packages underneath. 394 | In this exercise, place your tests in the `clubs` package and make sure there are no classes 395 | outside of it. 396 | 397 | === REST API (30 minutes) 398 | 399 | Micronaut helps you writing both the client and server sides of a REST API. In this service, 400 | we are going to create the following: 401 | 402 | ifdef::generate-diagrams[] 403 | [plantuml, clubs-diagram, png] 404 | .... 405 | ClubsApi <> 406 | ClubsClient <> 407 | 408 | ClubsApi <|-- ClubsClient 409 | ClubsApi <|-- ClubController 410 | 411 | ClubsApi : @Get("/") List listClubs() 412 | ClubsApi : @Get("/{id}") Club show(@NotNull Long id) 413 | ClubsApi : @Post("/") Club save(@NotNull String name, String stadium) 414 | .... 415 | endif::[] 416 | 417 | image::clubs-diagram.png[] 418 | 419 | icon:hand-o-right[] Create the `ClubsApi` interface, annotating its methods with 420 | `io.micronaut.http.annotation.Get` or `io.micronaut.http.annotation.Post` as described in the diagram. 421 | 422 | icon:hand-o-right[] Then, create `ClubsClient` by simply extending from `ClubsApi`. 423 | Annotate the interface with `io.micronaut.http.client.Client("/")`. 424 | 425 | icon:hand-o-right[] Finally, implement the controller `ClubController`. Annotate 426 | the class with `io.micronaut.http.annotation.Controller("/")`, matching the path 427 | specified on `ClubsClient`. Use `ClubRepository` to implement the actions by declaring 428 | a constructor dependency on it. 429 | 430 | WARNING: The controller actions need to be annotated with `@Get` / `@Post` again. 431 | 432 | icon:hand-o-right[] Finally, configure `logback.xml` to see some relevant output 433 | 434 | [source,xml] 435 | ---- 436 | include::./ex02/solution/clubs/src/main/resources/logback.xml[tag=!default] 437 | ---- 438 | <1> Debug level for our code 439 | <2> This allows to see the HTTP request and responses from the HTTP clients. 440 | 441 | icon:hand-o-right[] Once you have it, write an end-to-end test: 442 | 443 | +++
+++ 444 | Click to expand 445 | +++
+++ 446 | [source,java] 447 | ---- 448 | include::./ex02/solution/clubs/src/test/java/clubs/ClubControllerTest.java[tag=test] 449 | ---- 450 | +++
+++ 451 | 452 | === Load some data for production (15 minutes) 453 | 454 | During our tests, we have been seeding test data on demand, as it is a good 455 | practise to isolate test data from test to test. However, for production, we 456 | want some data loaded 457 | 458 | icon:hand-o-right[] Let's create a bean to load some data. Run: 459 | 460 | mn create-bean dataLoader 461 | 462 | icon:hand-o-right[] Change it to look like: 463 | 464 | +++
+++ 465 | Click to expand 466 | +++
+++ 467 | [source,java] 468 | ---- 469 | include::./ex02/solution/clubs/src/main/java/clubs/init/DataLoader.java[tag=class] 470 | ---- 471 | +++
+++ 472 | 473 | icon:hand-o-right[] Now, run the application: 474 | 475 | ./gradlew run 476 | 477 | icon:hand-o-right[] And make a request to `0:8080/` to see the results: 478 | 479 | === Register the service in Consul (10 minutes) 480 | 481 | We want the `clubs` microservice to be discoverable by the `fixtures` service. 482 | So we will enable Micronaut's Consul support for service discovery. 483 | 484 | icon:hand-o-right[] First, add the neccessary dependency in `build.gradle`: 485 | 486 | [source,java] 487 | ---- 488 | include::./ex02/solution/clubs/build.gradle[tag=dep,indent=0] 489 | ---- 490 | 491 | icon:hand-o-right[] Then, change `src/main/resources/application.yml` to define 492 | the Consul configuration: 493 | 494 | [source,yaml] 495 | ---- 496 | include::./ex02/solution/clubs/src/main/resources/application.yml[tag=consul] 497 | ---- 498 | 499 | icon:hand-o-right[] Finally, run a Consul instance with Docker: 500 | 501 | $ docker run -d --name=dev-consul -e CONSUL_BIND_INTERFACE=eth0 -e CONSUL_UI_BETA=true -p 8500:8500 consul 502 | 503 | icon:hand-o-right[] Now, if you run the application, you will see it registers 504 | with Consul at startup: 505 | 506 | ---- 507 | $ ./gradlew run 508 | ... 509 | 04:20:09.501 [nioEventLoopGroup-1-3] INFO i.m.d.registration.AutoRegistration - Registered service [clubs] with Consul 510 | ... 511 | ---- 512 | 513 | icon:hand-o-right[] If you go the http://localhost:8500/[Consul UI], you can see 514 | it shows as registered: 515 | 516 | image::consul.png[] 517 | 518 | icon:hand-o-right[] You can run yet another instance of `clubs` on a different 519 | shell, and see it registered. In order to do that, you need to tell Micronaut to 520 | run on a random port. In `application.yml`: 521 | 522 | [source,yaml] 523 | ---- 524 | include::./ex02/solution/clubs/src/main/resources/application.yml[tag=random-port] 525 | ---- 526 | 527 | We will use them both with Micronaut's load-balanced 528 | HTTP client in the next exercise. 529 | 530 | == Creating the Fixtures microservice (70 minutes) 531 | 532 | TIP: Change to the `ex03` directory to work on this exercise. 533 | 534 | icon:hand-o-right[] In this exercise we are creating the `fixtures` microservice: 535 | 536 | mn create-app --features=mongo-reactive,discovery-consul fixtures 537 | 538 | Once again, follow the steps of exercise 1 to add Micronaut Test to this project. Also, 539 | remove the `de.flapdoodle.embed.mongo` dependency, as we are using a Dockerized MongoDB 540 | instance. 541 | 542 | === Data layer (35 minutes) 543 | 544 | icon:hand-o-right[] First of all, run MongoDB with Docker: 545 | 546 | $ docker run -d --name=dev-mongo -p 27017:27017 mongo 547 | 548 | icon:hand-o-right[] Then, create the `Fixture` domain class with the following properties: 549 | 550 | [source,java] 551 | ---- 552 | include::./ex03/solution/fixtures/src/main/java/fixtures/domain/Fixture.java[tag=fields,indent=0] 553 | ---- 554 | 555 | As you can see, we are only storing club's ids. When rendering fixture details, 556 | we will use Micronaut's HTTP client to fetch details from the `clubs` microservice. 557 | 558 | icon:hand-o-right[] We also need a constructor with annotations that allow `Fixture` instances to be marshalled and unmarshalled 559 | to/from JSON and as a MongoDB document: 560 | 561 | [source,java] 562 | ---- 563 | include::./ex03/solution/fixtures/src/main/java/fixtures/domain/Fixture.java[tag=constructor,indent=0] 564 | ---- 565 | 566 | Be sure to add all the getter and setters as well. 567 | 568 | icon:hand-o-right[] The next thing we need is an HTTP client for the `clubs` microservice. Create one with: 569 | 570 | $ mn create-client clubs 571 | 572 | Before actually mapping any endpoint, we are going to create the following hierarchy: 573 | 574 | ifdef::generate-diagrams[] 575 | [plantuml, clients-diagram, png] 576 | .... 577 | ClubsApi <> 578 | ClubsClient <> 579 | 580 | ClubsApi <|-- ClubsClient 581 | ClubsApi <|-- ClubsClientMock 582 | 583 | ClubsApi : @Get("/{id}") Club show(Long id) 584 | .... 585 | endif::[] 586 | 587 | image::clients-diagram.png[] 588 | 589 | * `ClubsApi` is the interface that contains the client endpoint mappings. 590 | * `ClubsClient` is the production client, is annotated with `@Client` and simply 591 | extends from `ClubsApi`. 592 | * `ClubsClientMock` is a mocking client (resides within `src/test/java`), is annotated 593 | with `@Fallback`, and implements `ClubsApi` by returning hardcoded instances. 594 | 595 | This is how `ClubsApi` looks like: 596 | 597 | [source,java] 598 | ---- 599 | include::./ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsApi.java[tag=class] 600 | ---- 601 | 602 | We are using a reactive type in the HTTP client response, so that is a hint 603 | for Micronaut to make it non-blocking. 604 | 605 | Then, the production client: 606 | 607 | [source,java] 608 | ---- 609 | include::./ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsClient.java[tag=class] 610 | ---- 611 | <1> `"clubs"` is the Consul name for the Clubs microservice (which registers 612 | itself with the `micronaut.application.name` property). 613 | 614 | Finally, the mocking client: 615 | 616 | [source,java] 617 | ---- 618 | include::./ex03/solution/fixtures/src/test/java/fixtures/ClubsClientMock.java[tag=class] 619 | ---- 620 | 621 | icon:hand-o-right[] We also need a `Club` POJO to capture the JSON response from `clubs`. Define 622 | it with 2 string fields: `name` and `stadium`, and its constructor, getters, etc. 623 | 624 | icon:hand-o-right[] Now let's create a repository for `Fixture`. Following the same convention as 625 | in the previous exercise, begin with an interface: 626 | 627 | [source,java] 628 | ---- 629 | include::./ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepository.java[tag=class,indent=0] 630 | ---- 631 | 632 | icon:hand-o-right[] Then, the implementation: 633 | 634 | +++
+++ 635 | Click to expand 636 | +++
+++ 637 | [source,java] 638 | ---- 639 | include::./ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepositoryImpl.java[tag=class,indent=0] 640 | ---- 641 | +++
+++ 642 | 643 | icon:hand-o-right[] And a test: 644 | 645 | +++
+++ 646 | Click to expand 647 | +++
+++ 648 | [source,java] 649 | ---- 650 | include::./ex03/solution/fixtures/src/test/java/fixtures/FixtureRepositoryImplTest.java[tag=class,indent=0] 651 | ---- 652 | +++
+++ 653 | 654 | Make sure it passes. 655 | 656 | === REST API (35 minutes) 657 | 658 | icon:hand-o-right[] Let's create a controller for displaying fixtures: 659 | 660 | $ mn create-controller fixture 661 | 662 | As it was said earlier, our `Fixture` class doesn't store club names, but their id's (with the intention of 663 | having this microservice call the other). Therefore, we need a DTO class to represent what our JSON response 664 | is going to look like. 665 | 666 | icon:hand-o-right[] Create a POJO named `FixtureResponse` with the following attributes: 667 | 668 | [source,java] 669 | ---- 670 | include::./ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureResponse.java[tag=fields,indent=0] 671 | ---- 672 | 673 | Now we need a service that transforms a `Fixture` into a `FixtureResponse`. To do so, it need to make 674 | 2 HTTP calls to the `clubs` microservice, to get the name of each clubs. It will use `ClubsClient` for that. 675 | 676 | icon:hand-o-right[] Create a `FixtureService` like this: 677 | 678 | +++
+++ 679 | Click to expand 680 | +++
+++ 681 | [source,java] 682 | ---- 683 | include::./ex03/solution/fixtures/src/main/java/fixtures/service/FixtureService.java[tag=class] 684 | ---- 685 | +++
+++ 686 | 687 | icon:hand-o-right[] And write a test for it: 688 | 689 | +++
+++ 690 | Click to expand 691 | +++
+++ 692 | [source,java] 693 | ---- 694 | include::./ex03/solution/fixtures/src/test/java/fixtures/FixtureServiceTest.java[tag=class] 695 | ---- 696 | +++
+++ 697 | 698 | Finally, we need the REST controller that connect the dots. 699 | 700 | icon:hand-o-right[] Create a `FixtureController` that uses `FixtureRepository` and `FixtureService` as 701 | collaborators to produce a `Flowable` response: 702 | 703 | [source,java] 704 | ---- 705 | include::./ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureController.java[tag=action,indent=0] 706 | ---- 707 | 708 | === Load some data and run the application (10 minutes) 709 | 710 | icon:hand-o-right[] Similarly to the previous exercise, seed the application with some data. 711 | 712 | Also, we need to set the `micronaut.server.port` configuration property a value other than 8080, 713 | otherwise, we won't be able to run both services. 714 | 715 | icon:hand-o-right[] In `application.yml`, set `micronaut.server.port` to `8081` 716 | 717 | 718 | icon:hand-o-right[] Now, run the application: 719 | 720 | ./gradlew run 721 | 722 | If you make a request to the default controller, and the `clubs` microservice is not running, 723 | you will see an error: 724 | 725 | ---- 726 | {"message":"Internal Server Error: No available services for ID: clubs"} 727 | ---- 728 | 729 | icon:hand-o-right[] Now, run the `clubs` service on a different terminal, and try the request again. 730 | -------------------------------------------------------------------------------- /adoc.sh: -------------------------------------------------------------------------------- 1 | #Generate diagrams 2 | asciidoctor -r asciidoctor-diagram -a generate-diagrams README.adoc 3 | 4 | #Generate HTML 5 | asciidoctor -a source-highlighter=pygments README.adoc -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | git commit -a -m "Updated docs" 4 | set -e 5 | git push origin master 6 | 7 | rm -rf /tmp/images 8 | 9 | #Generate diagrams 10 | asciidoctor -r asciidoctor-diagram -a generate-diagrams README.adoc 11 | 12 | #Generate HTML 13 | asciidoctor -a source-highlighter=pygments README.adoc 14 | 15 | mv images /tmp 16 | 17 | git checkout -f gh-pages 18 | mv /tmp/images/* images/ 19 | mv README.html index.html 20 | git add -A 21 | git commit -m "Updated docs" 22 | git push origin gh-pages 23 | git checkout master -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .gradle 4 | build/ 5 | target/ 6 | out/ 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .project 12 | .settings 13 | .classpath -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim 2 | COPY build/libs/*.jar hello-galaxy.jar 3 | EXPOSE 8080 4 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar hello-galaxy.jar -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | id "java" 4 | id "net.ltgt.apt-eclipse" version "0.21" 5 | id "net.ltgt.apt-idea" version "0.21" 6 | id "com.github.johnrengelman.shadow" version "4.0.2" 7 | id "application" 8 | } 9 | 10 | 11 | 12 | version "0.1" 13 | group "hello.galaxy" 14 | 15 | repositories { 16 | mavenCentral() 17 | maven { url "https://jcenter.bintray.com" } 18 | } 19 | 20 | dependencyManagement { 21 | imports { 22 | mavenBom 'io.micronaut:micronaut-bom:1.1.0' 23 | } 24 | } 25 | 26 | configurations { 27 | // for dependencies that are needed for development only 28 | developmentOnly 29 | } 30 | 31 | dependencies { 32 | annotationProcessor "io.micronaut:micronaut-inject-java" 33 | annotationProcessor "io.micronaut:micronaut-validation" 34 | compile "io.micronaut:micronaut-inject" 35 | compile "io.micronaut:micronaut-validation" 36 | compile "io.micronaut:micronaut-runtime" 37 | compile "io.micronaut:micronaut-http-client" 38 | compile "io.micronaut:micronaut-http-server-netty" 39 | runtime "ch.qos.logback:logback-classic:1.2.3" 40 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 41 | testCompile "org.junit.jupiter:junit-jupiter-api" 42 | testCompile "io.micronaut.test:micronaut-test-junit5" 43 | testRuntime "org.junit.jupiter:junit-jupiter-engine" 44 | } 45 | 46 | test.classpath += configurations.developmentOnly 47 | 48 | mainClassName = "hello.galaxy.Application" 49 | // use JUnit 5 platform 50 | test { 51 | useJUnitPlatform() 52 | } 53 | tasks.withType(JavaCompile){ 54 | options.encoding = "UTF-8" 55 | options.compilerArgs.add('-parameters') 56 | } 57 | 58 | shadowJar { 59 | mergeServiceFiles() 60 | } 61 | 62 | run.classpath += configurations.developmentOnly 63 | run.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote') 64 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarosanchez/micronaut-v1-workshop-java/b568a5f09c9b6c5fdfab73ca946c741dd7c78160/ex01/solution/hello-galaxy/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | profile: service 2 | defaultPackage: hello.galaxy 3 | --- 4 | testFramework: junit 5 | sourceLanguage: java -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/src/main/java/hello/galaxy/Application.java: -------------------------------------------------------------------------------- 1 | package hello.galaxy; 2 | 3 | import io.micronaut.runtime.Micronaut; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | Micronaut.run(Application.class); 9 | } 10 | } -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/src/main/java/hello/galaxy/HelloController.java: -------------------------------------------------------------------------------- 1 | package hello.galaxy; 2 | 3 | import io.micronaut.http.annotation.Controller; 4 | import io.micronaut.http.annotation.Get; 5 | 6 | @Controller("/hello") 7 | public class HelloController { 8 | 9 | //tag::action[] 10 | @Get("/") 11 | String index() { 12 | return "Hello Galaxy!"; 13 | } 14 | //end::action[] 15 | 16 | } -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | micronaut: 2 | application: 3 | name: hello-galaxy 4 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ex01/solution/hello-galaxy/src/test/java/hello/galaxy/HelloControllerTest.java: -------------------------------------------------------------------------------- 1 | package hello.galaxy; 2 | 3 | import io.micronaut.http.client.RxHttpClient; 4 | import io.micronaut.runtime.server.EmbeddedServer; 5 | import io.micronaut.test.annotation.MicronautTest; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import javax.inject.Inject; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | @MicronautTest 13 | public class HelloControllerTest { 14 | 15 | @Inject 16 | private EmbeddedServer embeddedServer; 17 | 18 | @Test 19 | void testHelloGalaxy() { 20 | try(RxHttpClient client = embeddedServer.getApplicationContext().createBean(RxHttpClient.class, embeddedServer.getURL())) { 21 | assertEquals("Hello Galaxy!", client.toBlocking().exchange("/hello", String.class).body()); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /ex02/solution/clubs/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .gradle 4 | build/ 5 | target/ 6 | out/ 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .project 12 | .settings 13 | .classpath -------------------------------------------------------------------------------- /ex02/solution/clubs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim 2 | COPY build/libs/*.jar clubs.jar 3 | EXPOSE 8080 4 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar clubs.jar -------------------------------------------------------------------------------- /ex02/solution/clubs/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | id "com.github.johnrengelman.shadow" version "4.0.2" 4 | id "application" 5 | id "java" 6 | id "net.ltgt.apt-eclipse" version "0.21" 7 | id "net.ltgt.apt-idea" version "0.21" 8 | } 9 | 10 | 11 | 12 | version "0.1" 13 | group "clubs" 14 | 15 | repositories { 16 | mavenCentral() 17 | maven { url "https://jcenter.bintray.com" } 18 | } 19 | 20 | dependencyManagement { 21 | imports { 22 | mavenBom 'io.micronaut:micronaut-bom:1.1.0' 23 | } 24 | } 25 | 26 | configurations { 27 | // for dependencies that are needed for development only 28 | developmentOnly 29 | } 30 | 31 | dependencies { 32 | annotationProcessor "io.micronaut:micronaut-inject-java" 33 | annotationProcessor "io.micronaut:micronaut-validation" 34 | compile "io.micronaut:micronaut-http-client" 35 | compile "io.micronaut:micronaut-inject" 36 | compile "io.micronaut:micronaut-validation" 37 | compile "io.micronaut:micronaut-runtime" 38 | compile "io.micronaut.configuration:micronaut-hibernate-jpa" 39 | compile "io.micronaut.configuration:micronaut-jdbc-tomcat" 40 | compile "io.micronaut:micronaut-http-server-netty" 41 | 42 | //tag::dep[] 43 | compile "io.micronaut:micronaut-discovery-client" 44 | //end::dep[] 45 | 46 | runtime "ch.qos.logback:logback-classic:1.2.3" 47 | runtime "com.h2database:h2" 48 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 49 | testCompile "org.junit.jupiter:junit-jupiter-api" 50 | testCompile "io.micronaut.test:micronaut-test-junit5" 51 | testRuntime "org.junit.jupiter:junit-jupiter-engine" 52 | } 53 | 54 | test.classpath += configurations.developmentOnly 55 | 56 | mainClassName = "clubs.Application" 57 | // use JUnit 5 platform 58 | test { 59 | useJUnitPlatform() 60 | } 61 | 62 | shadowJar { 63 | mergeServiceFiles() 64 | } 65 | 66 | run.classpath += configurations.developmentOnly 67 | run.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote') 68 | tasks.withType(JavaCompile){ 69 | options.encoding = "UTF-8" 70 | options.compilerArgs.add('-parameters') 71 | } 72 | -------------------------------------------------------------------------------- /ex02/solution/clubs/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarosanchez/micronaut-v1-workshop-java/b568a5f09c9b6c5fdfab73ca946c741dd7c78160/ex02/solution/clubs/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ex02/solution/clubs/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ex02/solution/clubs/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /ex02/solution/clubs/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ex02/solution/clubs/micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | profile: service 2 | defaultPackage: clubs 3 | --- 4 | testFramework: junit 5 | sourceLanguage: java -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/Application.java: -------------------------------------------------------------------------------- 1 | package clubs; 2 | 3 | import io.micronaut.runtime.Micronaut; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | Micronaut.run(Application.class); 9 | } 10 | } -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/ClubsApi.java: -------------------------------------------------------------------------------- 1 | package clubs; 2 | 3 | import clubs.domain.Club; 4 | import io.micronaut.http.annotation.Get; 5 | import io.micronaut.http.annotation.Post; 6 | 7 | import javax.validation.constraints.NotNull; 8 | import java.util.List; 9 | 10 | public interface ClubsApi { 11 | 12 | @Get("/") 13 | List listClubs(); 14 | 15 | @Get("/{id}") 16 | Club show(@NotNull Long id); 17 | 18 | @Post("/") 19 | Club save(@NotNull String name, String stadium); 20 | } 21 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/client/ClubsClient.java: -------------------------------------------------------------------------------- 1 | package clubs.client; 2 | 3 | import clubs.ClubsApi; 4 | import io.micronaut.http.client.annotation.Client; 5 | 6 | @Client("/") 7 | public interface ClubsClient extends ClubsApi { 8 | } 9 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/controller/ClubController.java: -------------------------------------------------------------------------------- 1 | package clubs.controller; 2 | 3 | import clubs.domain.ClubRepository; 4 | import clubs.ClubsApi; 5 | import clubs.domain.Club; 6 | import io.micronaut.http.annotation.Controller; 7 | import io.micronaut.validation.Validated; 8 | 9 | import javax.validation.constraints.NotNull; 10 | import java.util.List; 11 | 12 | @Controller("/") 13 | @Validated 14 | public class ClubController implements ClubsApi { 15 | 16 | private ClubRepository repository; 17 | 18 | public ClubController(ClubRepository repository) { 19 | this.repository = repository; 20 | } 21 | 22 | @Override 23 | public List listClubs() { 24 | return repository.findAll(); 25 | } 26 | 27 | @Override 28 | public Club show(@NotNull Long id) { 29 | return repository.find(id).orElse(null); 30 | } 31 | 32 | @Override 33 | public Club save(@NotNull String name, String stadium) { 34 | return repository.save(name, stadium); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/domain/Club.java: -------------------------------------------------------------------------------- 1 | package clubs.domain; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.validation.constraints.NotNull; 8 | 9 | @Entity 10 | public class Club { 11 | 12 | public Club() {} 13 | 14 | public Club(@NotNull String name) { 15 | this.name = name; 16 | } 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.AUTO) 20 | private Long id; 21 | 22 | @NotNull 23 | private String name; 24 | 25 | private String stadium; 26 | 27 | public Long getId() { 28 | return id; 29 | } 30 | 31 | public void setId(Long id) { 32 | this.id = id; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public void setName(String name) { 40 | this.name = name; 41 | } 42 | 43 | public String getStadium() { 44 | return stadium; 45 | } 46 | 47 | public void setStadium(String stadium) { 48 | this.stadium = stadium; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/domain/ClubRepository.java: -------------------------------------------------------------------------------- 1 | package clubs.domain; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import javax.validation.constraints.NotNull; 5 | import java.util.List; 6 | import java.util.Optional; 7 | 8 | public interface ClubRepository { 9 | 10 | //tag::operations[] 11 | Long count(); 12 | Club save(@NotBlank String name, String stadium); 13 | List findAll(); 14 | Optional find(@NotNull Long id); 15 | //end::operations[] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/domain/ClubRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package clubs.domain; 2 | 3 | import io.micronaut.spring.tx.annotation.Transactional; 4 | 5 | import javax.inject.Singleton; 6 | import javax.persistence.EntityManager; 7 | import javax.persistence.PersistenceContext; 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | //tag::class[] 14 | @Singleton 15 | public class ClubRepositoryImpl implements ClubRepository { 16 | 17 | @PersistenceContext 18 | private EntityManager entityManager; 19 | 20 | @Override 21 | @Transactional(readOnly = true) 22 | public Long count() { 23 | return entityManager.createQuery("select count(c) from Club c", Long.class).getSingleResult(); 24 | } 25 | 26 | @Override 27 | @Transactional 28 | public Club save(@NotBlank String name, String stadium) { 29 | Club club = new Club(name); 30 | club.setStadium(stadium); 31 | entityManager.persist(club); 32 | return club; 33 | } 34 | 35 | @Override 36 | @Transactional(readOnly = true) 37 | public List findAll() { 38 | return entityManager.createQuery("select c from Club c", Club.class).getResultList(); 39 | } 40 | 41 | @Override 42 | @Transactional(readOnly = true) 43 | public Optional find(@NotNull Long id) { 44 | return Optional.ofNullable(entityManager.find(Club.class, id)); 45 | } 46 | 47 | } 48 | //end::class[] 49 | 50 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/java/clubs/init/DataLoader.java: -------------------------------------------------------------------------------- 1 | package clubs.init; 2 | 3 | import clubs.domain.ClubRepository; 4 | import io.micronaut.context.annotation.Requires; 5 | import io.micronaut.context.env.Environment; 6 | import io.micronaut.context.event.ApplicationEventListener; 7 | import io.micronaut.runtime.server.event.ServerStartupEvent; 8 | 9 | import javax.inject.Singleton; 10 | 11 | //tag::class[] 12 | @Singleton 13 | @Requires(notEnv = Environment.TEST) 14 | public class DataLoader implements ApplicationEventListener { 15 | 16 | private ClubRepository repository; 17 | 18 | public DataLoader(ClubRepository repository) { 19 | this.repository = repository; 20 | } 21 | 22 | @Override 23 | public void onApplicationEvent(ServerStartupEvent event) { 24 | if (repository.count() == 0) { 25 | repository.save("Real Madrid CF", "Santiago Bernabeu"); 26 | repository.save("FC Barcelona", "Camp Nou"); 27 | repository.save("CD Leganes", "Butarque"); 28 | repository.save("Getafe CF", "Coliseum"); 29 | } 30 | } 31 | } 32 | //end::class[] 33 | 34 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | #tag::random-port[] 3 | micronaut: 4 | server: 5 | port: -1 6 | #end::random-port[] 7 | application: 8 | name: clubs 9 | 10 | --- 11 | datasources: 12 | default: 13 | url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE 14 | driverClassName: org.h2.Driver 15 | username: sa 16 | password: '' 17 | #tag::jpa[] 18 | jpa: 19 | default: 20 | packages-to-scan: 21 | - 'clubs.domain' 22 | properties: 23 | hibernate: 24 | hbm2ddl: 25 | auto: update 26 | show_sql: true 27 | #end::jpa[] 28 | 29 | 30 | --- 31 | datasources.default: {} 32 | 33 | #tag::consul[] 34 | --- 35 | consul: 36 | client: 37 | registration: 38 | enabled: true 39 | defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}" 40 | #end::consul[] 41 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/test/java/clubs/ClubControllerTest.java: -------------------------------------------------------------------------------- 1 | package clubs; 2 | 3 | import clubs.client.ClubsClient; 4 | import clubs.domain.Club; 5 | import io.micronaut.test.annotation.MicronautTest; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import javax.inject.Inject; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | //tag::test[] 13 | @MicronautTest 14 | public class ClubControllerTest { 15 | 16 | @Inject 17 | ClubsClient client; 18 | 19 | @Test 20 | void testGetOneClub() { 21 | Club realMadrid = client.save("Real Madrid", "Santiago Bernabeu"); 22 | 23 | Club response = client.show(realMadrid.getId()); 24 | assertEquals("Santiago Bernabeu", response.getStadium()); 25 | } 26 | 27 | @Test 28 | void testFindAllClubs() { 29 | client.save("Real Madrid", "Santiago Bernabeu"); 30 | client.save("FC Barcelona", "Camp Nou"); 31 | 32 | assertEquals(2, client.listClubs().size()); 33 | } 34 | } 35 | //end::test[] 36 | 37 | -------------------------------------------------------------------------------- /ex02/solution/clubs/src/test/java/clubs/ClubRepositoryImplTest.java: -------------------------------------------------------------------------------- 1 | package clubs; 2 | 3 | import clubs.domain.Club; 4 | import clubs.domain.ClubRepository; 5 | import io.micronaut.test.annotation.MicronautTest; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import javax.inject.Inject; 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertSame; 14 | 15 | //tag::test[] 16 | @MicronautTest 17 | public class ClubRepositoryImplTest { 18 | 19 | @Inject 20 | ClubRepository repository; 21 | 22 | @Test 23 | void testCrudOperations() { 24 | assertEquals(0L, repository.count().longValue()); 25 | 26 | repository.save("Real Madrid", "Santiago Bernabeu"); 27 | repository.save("FC Barcelona", "Camp Nou"); 28 | assertEquals(2L, repository.count().longValue()); 29 | 30 | List allClubs = repository.findAll(); 31 | assertEquals(2, allClubs.size()); 32 | 33 | Club realMadrid = repository.find(1L).get(); 34 | assertSame("Santiago Bernabeu", realMadrid.getStadium()); 35 | 36 | assertEquals(Optional.empty(), repository.find(27L)); 37 | } 38 | 39 | } 40 | //end::test[] 41 | 42 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .gradle 4 | build/ 5 | target/ 6 | out/ 7 | .idea 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .project 12 | .settings 13 | .classpath -------------------------------------------------------------------------------- /ex03/solution/fixtures/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11-openj9:jdk-11.0.1.13-alpine-slim 2 | COPY build/libs/*.jar fixtures.jar 3 | EXPOSE 8080 4 | CMD java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dcom.sun.management.jmxremote -noverify ${JAVA_OPTS} -jar fixtures.jar -------------------------------------------------------------------------------- /ex03/solution/fixtures/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "io.spring.dependency-management" version "1.0.6.RELEASE" 3 | id "com.github.johnrengelman.shadow" version "4.0.2" 4 | id "application" 5 | id "java" 6 | id "net.ltgt.apt-eclipse" version "0.21" 7 | id "net.ltgt.apt-idea" version "0.21" 8 | } 9 | 10 | 11 | 12 | version "0.1" 13 | group "fixtures" 14 | 15 | repositories { 16 | mavenCentral() 17 | maven { url "https://jcenter.bintray.com" } 18 | } 19 | 20 | dependencyManagement { 21 | imports { 22 | mavenBom 'io.micronaut:micronaut-bom:1.1.0' 23 | } 24 | } 25 | 26 | configurations { 27 | // for dependencies that are needed for development only 28 | developmentOnly 29 | } 30 | 31 | dependencies { 32 | annotationProcessor "io.micronaut:micronaut-inject-java" 33 | annotationProcessor "io.micronaut:micronaut-validation" 34 | compile "io.micronaut:micronaut-http-client" 35 | compile "io.micronaut.configuration:micronaut-mongo-reactive" 36 | compile "io.micronaut:micronaut-inject" 37 | compile "io.micronaut:micronaut-validation" 38 | compile "io.micronaut:micronaut-runtime" 39 | compile "io.micronaut:micronaut-discovery-client" 40 | compile "io.micronaut:micronaut-http-server-netty" 41 | runtime "ch.qos.logback:logback-classic:1.2.3" 42 | testAnnotationProcessor "io.micronaut:micronaut-inject-java" 43 | testCompile "org.junit.jupiter:junit-jupiter-api" 44 | testCompile "io.micronaut.test:micronaut-test-junit5" 45 | testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.0.1" 46 | testRuntime "org.junit.jupiter:junit-jupiter-engine" 47 | } 48 | 49 | test.classpath += configurations.developmentOnly 50 | 51 | mainClassName = "fixtures.Application" 52 | // use JUnit 5 platform 53 | test { 54 | useJUnitPlatform() 55 | } 56 | 57 | shadowJar { 58 | mergeServiceFiles() 59 | } 60 | 61 | run.classpath += configurations.developmentOnly 62 | run.jvmArgs('-noverify', '-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote') 63 | tasks.withType(JavaCompile){ 64 | options.encoding = "UTF-8" 65 | options.compilerArgs.add('-parameters') 66 | } 67 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarosanchez/micronaut-v1-workshop-java/b568a5f09c9b6c5fdfab73ca946c741dd7c78160/ex03/solution/fixtures/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ex03/solution/fixtures/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/micronaut-cli.yml: -------------------------------------------------------------------------------- 1 | profile: service 2 | defaultPackage: fixtures 3 | --- 4 | testFramework: junit 5 | sourceLanguage: java -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/Application.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import io.micronaut.runtime.Micronaut; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | Micronaut.run(Application.class); 9 | } 10 | } -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/clubs/Club.java: -------------------------------------------------------------------------------- 1 | package fixtures.clubs; 2 | 3 | public class Club { 4 | 5 | private String name; 6 | private String stadium; 7 | 8 | public Club() { 9 | } 10 | 11 | public Club(String name, String stadium) { 12 | this.name = name; 13 | this.stadium = stadium; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getStadium() { 25 | return stadium; 26 | } 27 | 28 | public void setStadium(String stadium) { 29 | this.stadium = stadium; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsApi.java: -------------------------------------------------------------------------------- 1 | package fixtures.clubs; 2 | 3 | import io.micronaut.http.annotation.Get; 4 | import io.reactivex.Maybe; 5 | 6 | //tag::class[] 7 | public interface ClubsApi { 8 | 9 | @Get("/{id}") 10 | Maybe findTeam(Long id); 11 | 12 | } 13 | //end::class[] 14 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/clubs/ClubsClient.java: -------------------------------------------------------------------------------- 1 | package fixtures.clubs; 2 | 3 | import io.micronaut.http.client.annotation.Client; 4 | 5 | //tag::class[] 6 | @Client("clubs") // <1> 7 | public interface ClubsClient extends ClubsApi {} 8 | //end::class[] 9 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureController.java: -------------------------------------------------------------------------------- 1 | package fixtures.controller; 2 | 3 | import fixtures.service.FixtureService; 4 | import fixtures.domain.FixtureRepository; 5 | import io.micronaut.http.annotation.Controller; 6 | import io.micronaut.http.annotation.Get; 7 | import io.reactivex.Flowable; 8 | 9 | @Controller("/") 10 | public class FixtureController { 11 | 12 | private FixtureRepository fixtureRepository; 13 | private FixtureService fixtureService; 14 | 15 | public FixtureController(FixtureRepository fixtureRepository, FixtureService fixtureService) { 16 | this.fixtureRepository = fixtureRepository; 17 | this.fixtureService = fixtureService; 18 | } 19 | 20 | //tag::action[] 21 | @Get("/") 22 | public Flowable list() { 23 | return fixtureRepository.findAll().flatMapMaybe(fixture -> fixtureService.toResponse(fixture)); 24 | } 25 | //end::action[] 26 | 27 | } -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/controller/FixtureResponse.java: -------------------------------------------------------------------------------- 1 | package fixtures.controller; 2 | 3 | import java.util.Date; 4 | 5 | public class FixtureResponse { 6 | 7 | //tag::fields[] 8 | private String homeClubName; 9 | private String awayClubName; 10 | 11 | private String stadium; 12 | 13 | private Short homeScore; 14 | private Short awayScore; 15 | 16 | private Date date; 17 | //end::fields[] 18 | 19 | public FixtureResponse() {} 20 | 21 | public FixtureResponse(String homeClubName, String awayClubName, String stadium, Short homeScore, Short awayScore, Date date) { 22 | this.homeClubName = homeClubName; 23 | this.awayClubName = awayClubName; 24 | this.stadium = stadium; 25 | this.homeScore = homeScore; 26 | this.awayScore = awayScore; 27 | this.date = date; 28 | } 29 | 30 | public String getHomeClubName() { 31 | return homeClubName; 32 | } 33 | 34 | public void setHomeClubName(String homeClubName) { 35 | this.homeClubName = homeClubName; 36 | } 37 | 38 | public String getAwayClubName() { 39 | return awayClubName; 40 | } 41 | 42 | public void setAwayClubName(String awayClubName) { 43 | this.awayClubName = awayClubName; 44 | } 45 | 46 | public String getStadium() { 47 | return stadium; 48 | } 49 | 50 | public void setStadium(String stadium) { 51 | this.stadium = stadium; 52 | } 53 | 54 | public Short getHomeScore() { 55 | return homeScore; 56 | } 57 | 58 | public void setHomeScore(Short homeScore) { 59 | this.homeScore = homeScore; 60 | } 61 | 62 | public Short getAwayScore() { 63 | return awayScore; 64 | } 65 | 66 | public void setAwayScore(Short awayScore) { 67 | this.awayScore = awayScore; 68 | } 69 | 70 | public Date getDate() { 71 | return date; 72 | } 73 | 74 | public void setDate(Date date) { 75 | this.date = date; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/domain/Fixture.java: -------------------------------------------------------------------------------- 1 | package fixtures.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import org.bson.codecs.pojo.annotations.BsonCreator; 6 | import org.bson.codecs.pojo.annotations.BsonId; 7 | import org.bson.codecs.pojo.annotations.BsonProperty; 8 | import org.bson.types.ObjectId; 9 | 10 | import java.util.Date; 11 | 12 | public class Fixture { 13 | 14 | //tag::fields[] 15 | @BsonId 16 | private ObjectId id; 17 | 18 | private Long homeClubId; 19 | private Long awayClubId; 20 | 21 | private Short homeScore; 22 | private Short awayScore; 23 | 24 | private Date date; 25 | //end::fields[] 26 | 27 | 28 | //tag::constructor[] 29 | @BsonCreator 30 | @JsonCreator 31 | public Fixture(@BsonProperty("homeClubId") @JsonProperty("homeClubId") Long homeClubId, 32 | @BsonProperty("awayClubId") @JsonProperty("awayClubId") Long awayClubId, 33 | @BsonProperty("homeScore") @JsonProperty("homeScore") Short homeScore, 34 | @BsonProperty("awayScore") @JsonProperty("awayScore") Short awayScore, 35 | @BsonProperty("date") @JsonProperty("date") Date date) { 36 | this.homeClubId = homeClubId; 37 | this.awayClubId = awayClubId; 38 | this.homeScore = homeScore; 39 | this.awayScore = awayScore; 40 | this.date = date; 41 | } 42 | //end::constructor[] 43 | 44 | 45 | public ObjectId getId() { 46 | return id; 47 | } 48 | 49 | public void setId(ObjectId id) { 50 | this.id = id; 51 | } 52 | 53 | public Long getHomeClubId() { 54 | return homeClubId; 55 | } 56 | 57 | public void setHomeClubId(Long homeClubId) { 58 | this.homeClubId = homeClubId; 59 | } 60 | 61 | public Long getAwayClubId() { 62 | return awayClubId; 63 | } 64 | 65 | public void setAwayClubId(Long awayClubId) { 66 | this.awayClubId = awayClubId; 67 | } 68 | 69 | public Short getHomeScore() { 70 | return homeScore; 71 | } 72 | 73 | public void setHomeScore(Short homeScore) { 74 | this.homeScore = homeScore; 75 | } 76 | 77 | public Short getAwayScore() { 78 | return awayScore; 79 | } 80 | 81 | public void setAwayScore(Short awayScore) { 82 | this.awayScore = awayScore; 83 | } 84 | 85 | public Date getDate() { 86 | return date; 87 | } 88 | 89 | public void setDate(Date date) { 90 | this.date = date; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepository.java: -------------------------------------------------------------------------------- 1 | package fixtures.domain; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Single; 5 | 6 | import javax.validation.Valid; 7 | 8 | //tag::class[] 9 | public interface FixtureRepository { 10 | 11 | Single save(@Valid Fixture fixture); 12 | Flowable findAll(); 13 | Single count(); 14 | 15 | } 16 | //end::class[] 17 | 18 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/domain/FixtureRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package fixtures.domain; 2 | 3 | import com.mongodb.reactivestreams.client.MongoClient; 4 | import com.mongodb.reactivestreams.client.MongoCollection; 5 | import io.reactivex.Flowable; 6 | import io.reactivex.Single; 7 | 8 | import javax.inject.Singleton; 9 | import javax.validation.Valid; 10 | 11 | //tag::class[] 12 | @Singleton 13 | public class FixtureRepositoryImpl implements FixtureRepository { 14 | 15 | public static final String DB_NAME = "fixturesDb"; 16 | public static final String COLLECTION_NAME = "fixtures"; 17 | 18 | private MongoClient mongoClient; 19 | 20 | public FixtureRepositoryImpl(MongoClient mongoClient) { 21 | this.mongoClient = mongoClient; 22 | } 23 | 24 | @Override 25 | public Single save(@Valid Fixture fixture) { 26 | return Single.fromPublisher(getCollection().insertOne(fixture)).map(success -> fixture); 27 | } 28 | 29 | @Override 30 | public Flowable findAll() { 31 | return Flowable.fromPublisher(getCollection().find()); 32 | } 33 | 34 | @Override 35 | public Single count() { 36 | return Single.fromPublisher(getCollection().count()); 37 | } 38 | 39 | private MongoCollection getCollection() { 40 | return mongoClient.getDatabase(DB_NAME).getCollection(COLLECTION_NAME, Fixture.class); 41 | } 42 | } 43 | //end::class[] 44 | 45 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/init/DataLoader.java: -------------------------------------------------------------------------------- 1 | package fixtures.init; 2 | 3 | import fixtures.domain.Fixture; 4 | import fixtures.domain.FixtureRepository; 5 | import io.micronaut.context.annotation.Requires; 6 | import io.micronaut.context.env.Environment; 7 | import io.micronaut.context.event.ApplicationEventListener; 8 | import io.micronaut.runtime.server.event.ServerStartupEvent; 9 | 10 | import javax.inject.Singleton; 11 | import java.util.Date; 12 | 13 | //tag::class[] 14 | @Singleton 15 | @Requires(notEnv = Environment.TEST) 16 | public class DataLoader implements ApplicationEventListener { 17 | 18 | private FixtureRepository repository; 19 | 20 | public DataLoader(FixtureRepository repository) { 21 | this.repository = repository; 22 | } 23 | 24 | @Override 25 | public void onApplicationEvent(ServerStartupEvent event) { 26 | if (repository.count().blockingGet() == 0) { 27 | repository.save(new Fixture(1L, 2L, (short)5, (short)0, new Date())); 28 | } 29 | } 30 | } 31 | //end::class[] 32 | 33 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/java/fixtures/service/FixtureService.java: -------------------------------------------------------------------------------- 1 | package fixtures.service; 2 | 3 | import fixtures.clubs.ClubsClient; 4 | import fixtures.controller.FixtureResponse; 5 | import fixtures.domain.Fixture; 6 | import io.reactivex.Maybe; 7 | 8 | import javax.inject.Singleton; 9 | 10 | //tag::class[] 11 | @Singleton 12 | public class FixtureService { 13 | 14 | private ClubsClient clubsClient; 15 | 16 | public FixtureService(ClubsClient clubsClient) { 17 | this.clubsClient = clubsClient; 18 | } 19 | 20 | public Maybe toResponse(Fixture fixture) { 21 | return Maybe.zip( 22 | clubsClient.findTeam(fixture.getHomeClubId()), 23 | clubsClient.findTeam(fixture.getAwayClubId()), 24 | (homeClub, awayClub) -> new FixtureResponse(homeClub.getName(), 25 | awayClub.getName(), 26 | homeClub.getStadium(), 27 | fixture.getHomeScore(), 28 | fixture.getAwayScore(), 29 | fixture.getDate()) 30 | ); 31 | } 32 | } 33 | //end::class[] 34 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | micronaut: 3 | server: 4 | port: 8081 5 | application: 6 | name: fixtures 7 | 8 | --- 9 | mongodb: 10 | uri: "mongodb://${MONGO_HOST:localhost}:${MONGO_PORT:27017}" 11 | 12 | consul: 13 | client: 14 | registration: 15 | enabled: true 16 | defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}" -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/test/java/fixtures/ClubsClientMock.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import fixtures.clubs.Club; 4 | import fixtures.clubs.ClubsApi; 5 | import io.micronaut.retry.annotation.Fallback; 6 | import io.reactivex.Maybe; 7 | 8 | //tag::class[] 9 | @Fallback 10 | public class ClubsClientMock implements ClubsApi { 11 | @Override 12 | public Maybe findTeam(Long id) { 13 | if (id == 1) { 14 | return Maybe.just(new Club("CD Leganes", "Butarque")); 15 | } else { 16 | return Maybe.just(new Club("Getafe CF", "Coliseum")); 17 | } 18 | } 19 | } 20 | //end::class[] 21 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/test/java/fixtures/FixtureClient.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import fixtures.controller.FixtureResponse; 4 | import io.micronaut.http.annotation.Get; 5 | import io.micronaut.http.client.annotation.Client; 6 | import io.reactivex.Flowable; 7 | 8 | @Client("/") 9 | public interface FixtureClient { 10 | @Get("/") 11 | Flowable list(); 12 | } 13 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/test/java/fixtures/FixtureControllerTest.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import com.mongodb.reactivestreams.client.MongoClient; 4 | import fixtures.controller.FixtureResponse; 5 | import fixtures.domain.Fixture; 6 | import fixtures.domain.FixtureRepository; 7 | import io.micronaut.test.annotation.MicronautTest; 8 | import io.reactivex.Flowable; 9 | import org.bson.Document; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import javax.inject.Inject; 14 | 15 | import java.util.Date; 16 | 17 | import static fixtures.domain.FixtureRepositoryImpl.COLLECTION_NAME; 18 | import static fixtures.domain.FixtureRepositoryImpl.DB_NAME; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | @MicronautTest 22 | public class FixtureControllerTest { 23 | 24 | @Inject 25 | FixtureClient fixtureClient; 26 | 27 | @Inject 28 | FixtureRepository repository; 29 | 30 | @Inject 31 | MongoClient mongoClient; 32 | 33 | @BeforeEach 34 | void cleanup() { 35 | Flowable.fromPublisher(mongoClient.getDatabase(DB_NAME).getCollection(COLLECTION_NAME, Fixture.class).deleteMany(new Document())).blockingFirst(); 36 | } 37 | 38 | @Test 39 | void testList() { 40 | repository.save(new Fixture(1L, 2L, (short)5, (short)0, new Date())).blockingGet(); 41 | 42 | FixtureResponse response = fixtureClient.list().toList().blockingGet().get(0); 43 | assertEquals("CD Leganes", response.getHomeClubName()); 44 | assertEquals("Getafe CF", response.getAwayClubName()); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/test/java/fixtures/FixtureRepositoryImplTest.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import com.mongodb.reactivestreams.client.MongoClient; 4 | import fixtures.domain.Fixture; 5 | import fixtures.domain.FixtureRepository; 6 | import io.micronaut.test.annotation.MicronautTest; 7 | import io.reactivex.Flowable; 8 | import org.bson.Document; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import javax.inject.Inject; 13 | import java.util.Date; 14 | 15 | import static fixtures.domain.FixtureRepositoryImpl.COLLECTION_NAME; 16 | import static fixtures.domain.FixtureRepositoryImpl.DB_NAME; 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | 19 | //tag::class[] 20 | @MicronautTest 21 | public class FixtureRepositoryImplTest { 22 | 23 | @Inject 24 | FixtureRepository repository; 25 | 26 | @Inject 27 | MongoClient mongoClient; 28 | 29 | @BeforeEach 30 | void cleanup() { 31 | Flowable.fromPublisher(mongoClient.getDatabase(DB_NAME).getCollection(COLLECTION_NAME, Fixture.class).deleteMany(new Document())).blockingFirst(); 32 | } 33 | 34 | @Test 35 | void testCrud() { 36 | assertEquals(0, repository.count().blockingGet().longValue()); 37 | 38 | repository.save(new Fixture(1L, 2L, (short)5, (short)0, new Date())).blockingGet(); 39 | repository.save(new Fixture(3L, 4L, (short)5, (short)0, new Date())).blockingGet(); 40 | assertEquals(2, repository.count().blockingGet().longValue()); 41 | assertEquals(2, repository.findAll().toList().blockingGet().size()); 42 | } 43 | } 44 | //end::class[] 45 | 46 | -------------------------------------------------------------------------------- /ex03/solution/fixtures/src/test/java/fixtures/FixtureServiceTest.java: -------------------------------------------------------------------------------- 1 | package fixtures; 2 | 3 | import com.mongodb.reactivestreams.client.MongoClient; 4 | import fixtures.controller.FixtureResponse; 5 | import fixtures.domain.Fixture; 6 | import fixtures.domain.FixtureRepository; 7 | import fixtures.service.FixtureService; 8 | import io.micronaut.test.annotation.MicronautTest; 9 | import io.reactivex.Flowable; 10 | import org.bson.Document; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | 14 | import javax.inject.Inject; 15 | 16 | import java.util.Date; 17 | 18 | import static fixtures.domain.FixtureRepositoryImpl.COLLECTION_NAME; 19 | import static fixtures.domain.FixtureRepositoryImpl.DB_NAME; 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | 22 | //tag::class[] 23 | @MicronautTest 24 | public class FixtureServiceTest { 25 | 26 | @Inject 27 | FixtureService fixtureService; 28 | 29 | @Inject 30 | FixtureRepository repository; 31 | 32 | @Inject 33 | MongoClient mongoClient; 34 | 35 | @BeforeEach 36 | void cleanup() { 37 | Flowable.fromPublisher(mongoClient.getDatabase(DB_NAME).getCollection(COLLECTION_NAME, Fixture.class).deleteMany(new Document())).blockingFirst(); 38 | } 39 | 40 | @Test 41 | void testToResponse(){ 42 | Fixture fixture = repository.save(new Fixture(1L, 2L, (short)5, (short)0, new Date())).blockingGet(); 43 | FixtureResponse response = fixtureService.toResponse(fixture).blockingGet(); 44 | 45 | assertEquals("CD Leganes", response.getHomeClubName()); 46 | assertEquals("Getafe CF", response.getAwayClubName()); 47 | } 48 | 49 | } 50 | //end::class[] 51 | -------------------------------------------------------------------------------- /images/consul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alvarosanchez/micronaut-v1-workshop-java/b568a5f09c9b6c5fdfab73ca946c741dd7c78160/images/consul.png --------------------------------------------------------------------------------