JVM Advent

The JVM Programming Advent Calendar

Intro to Micronaut

Why Micronaut

Java was created in world different from the one today, at that time long running applications were common way and preferred way of working. Frameworks from that time would do all kind of stuff during startup, like scanning class-path, creating proxies, dependency injection as so on. This of course had result of longer startup time, however since application were suppose to run for long time this initial hit didn’t matter. For all that magic we also needed additional memory, again taken into account that applications were running on powerful machines this didn’t matter that much.

Fast forward today, and we arrive into world of micorservices and instant feedback for end users, where things as startup time and memory foot print matter a lot, due to need to scale up and down depending on the current load. Micronaut is “new” java framework, build by people who have experience in building older type of frameworks, they designed Micronaut with goal of keeping developer productivity, while addressing all drawbacks of previous frameworks under modern day requirement.

This is achieved in a very simple way. All the magic, like scanning class-path, creating proxies, dependency injection and so on that frameworks were doing in run time (startup), in Micronaut are done during compile time. This has direct result of faster startup time and lower memory footprint.

How to Start

Some frameworks have so called starter kits or websites, which can help you in setting up skeleton of a project. In order to use this in case of Micronaut, you need to install micronaut on your machine, and to do so first you need to install SDKMan.

Once you install SDKMan you can install Microanut using this command

$ sdk install micronaut

After this you can use micoranut command line tool to create skeleton application, for example something like this

$ mn create-app <app name> --build maven

First Project

Let us create first simple project which will have access to DB. We will do so by running this command

$ mn create-app intromicro --build maven

with option –features we can add support out of the box for Hibernate, security and so on, however in this example we will add all dependencies manually.

In our example we will use H2 as DB, so we need to add dependency for H2

<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
</dependency>

and also dependencies for DB layers

<dependency>
  <groupId>io.micronaut.data</groupId>
  <artifactId>micronaut-data-hibernate-jpa</artifactId>
  <version>1.0.0.M5</version>
</dependency>
<dependency>
  <groupId>io.micronaut.configuration</groupId>
  <artifactId>micronaut-hibernate-jpa</artifactId>
  <exclusions>
    <exclusion>  <!-- declare the exclusion here -->
      <groupId>io.micronaut.configuration</groupId>
      <artifactId>micronaut-hibernate-jpa-spring</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>io.micronaut.configuration</groupId>
  <artifactId>micronaut-jdbc-hikari</artifactId>
  <scope>runtime</scope>
</dependency>

We also need to extend annotationProcessorPaths of compile stage and test stage with this

     <annotationProcessorPaths>
       .....
       <path>
         <groupId>io.micronaut.data</groupId>
         <artifactId>micronaut-data-processor</artifactId>
         <version>1.0.0.M5</version>
       </path>
       ......      
     </annotationProcessorPaths>

Last but not the least we also need to update property file. Let us add this to application.yml file

---
datasources:
  default:
    url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    driverClassName: org.h2.Driver
    username: sa
    password: ''
    schema-generate: CREATE_DROP
    dialect: H2
jpa:
  default:
    properties:
      hibernate:
        bytecode:
          provider: none
        hbm2ddl:
          auto: update

Now that we have skeleton Micronaut application, let us build on top of it.

We are going to build very simple book store, with simple CRUD Rest API that is secured.

Code so far can be found here – https://github.com/vladimir-dejanovic/intro-to-micronaut-javaadvent-blogpost/tree/master/step1

Entities and DB Access

Let us create entity that will host all values for Books. Book class will be simple pojo with three fields id, title and author.

public class Book {

    private Long id;
    private String title;
    private String author;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

Let us make this pojo to an Entity in same way you would do it in any other framework.
Add Entity annotation, at top of class and appropriate annotations in class it self.

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotNull
 .....
}

Next thing that we need is repository. Thanks to all dependencies that we added it will be very easy task.
We just need to create interface as shown in this code

import io.micronaut.data.repository.CrudRepository;
import io.micronaut.data.annotation.Repository;
import xyz.itshark.blog.javaadvent.intromicro.pojo.Book;

@Repository
public interface BookRepository extends CrudRepository<Book,Long> {
}

As you can see, there is very little that we needed to do, we just needed to add annotation Repository, extend CrudRepository and provide classes for Entity and Primary Key, in our case Book and Long.

With this code, everything is taken care of DB, Entity and Repository for accessing DB.

Code so far can be found here – https://github.com/vladimir-dejanovic/intro-to-micronaut-javaadvent-blogpost/tree/master/step2

REST and Services

Let us now create REST API

One thing that I learned over the years is that you don’t need to have Services for you code to work, however it helps with maintainability and usability of code especially on bigger products. So let us create one very simple Book Service.

@Singleton
public class BookService {

    private BookRepository bookRepository;

    public BookService( BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public Optional findById(Long id) {
        return bookRepository.findById(id);
    }

    public List findAll() {
        return bookRepository.findAll();
    }

    public Book addBook(Book book) {
        return bookRepository.save(book);
    }
}

We added annotation Singleton on class level. As you might guessed this let Micronaut know that this class will be singleton, only one.

Next thing that we will look is dependency injection. There are multiple ways of using dependency injection in Micronaut, in this example we are using dependency injection over Constructor. In our case BookRepository will be injected into our BookService once BookService is created.

Your IDE will probably complain about signature of bookRepository.findAll, so let us fix that. We need to add this code to BookRepository interface

@Override
List findAll();

Now that this is fixed, we can focus on adding REST API. Let us create class BookRestController.

Once it is created add annotation Controller to let Micronaut know this will be Controller class in so called MVC pattern. Setup root path for our controller as “/book”, argument passed to annotation. Then we need to inject BookService over constructor, in similar way we did it in previous example

@Controller("/books")
public class BookRestController {

    private BookService bookService;

    public BookRestController(BookService bookService) {
        this.bookService = bookService;
    }
}

Once all of this is done, let us add some rest end points.

First let us add endpoint to retrieve all books in our book store.

    @Get("/")
    public List getAllBooks() {
        return bookService.findAll();
    }

Here we are registering code to react on GET request to “/books/”, remember that we added root path for Controller to /books/.

Next let us add endpoint to retrieve single Book record for corresponding ID.

    @Get("/{id}")
    public Book getBookById(@PathVariable("id") Long id) {
        return bookService.findById(id).orElse(null);
    }

Compare to previous example, here we added PathVariableannotation to method signature and also we added “{id}” as Get annotation argument.

Last end point that we will added to our REST API is end point to add data.

    @Post("/")
    public Book addBook(@Body Book book) {
        return bookService.addBook(book);
    }

Of course we are using Post method, and also are expecting for data load to be part of request body. That is why used Post annotation and Body annotation in method signature.

Let us now test if all is working as expected. If we hit /books/ end point with GET method we expect to get empty list.

$ curl http://localhost:8080/books/

[]

All good so far, let us now try to add some data, for that we will hit same URL with POST method.

$ curl -X POST -d '{"title":"1Q84","author":"Haruki Murakami"}' -H 'Content-type:application/json' http://localhost:8080/books/

{"id":1,"title":"1Q84","author":"Haruki Murakami"}

$ curl -X POST -d '{"title":"Dance Dance Dance","author":"Haruki Murakami"}' -H 'Content-type:application/json' http://localhost:8080/books/

{"id":2,"title":"Dance Dance Dance","author":"Haruki Murakami"}

If we call same end point again with GET we expect to get data that we have just put in

$ curl http://localhost:8080/books/

[{"id":1,"title":"1Q84","author":"Haruki Murakami"},{"id":2,"title":"Dance Dance Dance","author":"Haruki Murakami"}]

Let us now request book by id

$ curl http://localhost:8080/books/2

{"id":2,"title":"Dance Dance Dance","author":"Haruki Murakami"}

Code so far can be found here – https://github.com/vladimir-dejanovic/intro-to-micronaut-javaadvent-blogpost/tree/master/step3

Security

Now that we added basic application, let us secure it.

First we need to add additional dependency

    
      io.micronaut
      micronaut-security-session
      compile

Next we need to update application.yml

---
micronaut:
  security:
    enabled: true
    endpoints:
      login:
        enabled: true
      logout:
        enabled: true
    session:
      enabled: true
      loginSuccessTargetUrl: /
      loginFailureTargetUrl: /

To secure our end points we can just add annotation Secured. In order to make sure only authenticated users can add data to our app we need to add @Secured(SecurityRule.IS_AUTHENTICATED) to addBook method.

    @Post("/")
    @Secured(SecurityRule.IS_AUTHENTICATED)
    public Book addBook(@Body Book book) {
        return bookService.addBook(book);
    }

In order to allow users to still get info about books without any authentication we need to add @Secured(SecurityRule.IS_ANONYMOUS)

    @Get("/")
	  @Secured(SecurityRule.IS_ANONYMOUS)
    public List getAllBooks() {
        return bookService.findAll();
    }

    @Get("/{id}")
	  @Secured(SecurityRule.IS_ANONYMOUS)
    public Book getBookById(@PathVariable("id") Long id) {
        return bookService.findById(id).orElse(null);
    }

There are multiple ways to add security inside Micronaut, using annotations is one of them.

Next thing that we need to provide is authentication provider, which we will use to authenticate users.

Let us create class SimpleAuthenticationProvider which implements AuthenticationProvider. This class should also be singleton. Since this is just simple example we can create dummy authentication which is expecting for username to be user and password to be password.

@Singleton
public class SimpleAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Publisher authenticate(AuthenticationRequest authenticationRequest) {
        if(authenticationRequest.getIdentity().equals("user") && authenticationRequest.getSecret().equals("password")) {
            return Flowable.just(new UserDetails("user",new ArrayList()));
        } else return Flowable.just(new AuthenticationFailed());
    }
}

If we try again to insert data using post it should fail now

$ curl -v -X POST -d '{"title":"1Q84","author":"Haruki Murakami"}' -H 'Content-type:application/json' http://localhost:8080/books/

* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /books/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.52.1
> Accept: */*
> Content-type:application/json
> Content-Length: 43
>
* upload completely sent off: 43 out of 43 bytes
< HTTP/1.1 401 Unauthorized
< Date: Sat, 14 Dec 2019 17:27:31 GMT
< transfer-encoding: chunked
< connection: close
<
* Curl_http_done: called premature == 0
* Closing connection 0

as we can see, we are not authorized for this action. Let us try again, but this time provide username and password

$ curl -X POST -d '{"title":"After Dark","author":"Haruki Murakami"}' -H 'Content-type:application/json' http://localhost:8080/books/ -u 'user:password'

{"id":3,"title":"After Dark","author":"Haruki Murakami"}

let us check if reading still works without credentials

$ curl http://localhost:8080/books/

[{"id":1,"title":"1Q84","author":"Haruki Murakami"},{"id":2,"title":"Dance Dance Dance","author":"Haruki Murakami"},{"id":3,"title":"After Dark","author":"Haruki Murakami"}]

Code so far can be found here – https://github.com/vladimir-dejanovic/intro-to-micronaut-javaadvent-blogpost/tree/master/step4

Conclusion

As you can see developing applications in Micronaut is very productive from Developer point of view, beside this you get a lot of goodies out of the box from vast Micronaut ecosystem, and also will benefit from the start with faster startups, lower memory foot print, reactive systems and much more.

 

Remember what we covered in this article is just a small part of Micronaut and benefits you get from it.

Resources

Author: Vladimir Dejanovic

Founder and leader of AmsterdamJUG.
JavaOne Rock Star, CodeOne Star speaker
Storyteller

Software Architect ,Team Lead and IT Consultant working in industry since 2006 developing high performance software in multiple programming languages and technologies from desktop to mobile and web with high load traffic.

Enjoining developing software mostly in Java and JavaScript, however also wrote fair share of code in Scala, C++, C, PHP, Go, Objective-C, Python, R, Lisp and many others.

Always interested in cool new stuff, Free and Open Source software.

Like giving talks at conferences like JavaOne, Devoxx BE, Devoxx US, Devoxx PL, Devoxx MA, Java Day Istanbul, Java Day Minks, Voxxed Days Bristol, Voxxed Days Bucharest, Voxxed Days Belgrade, Voxxed Days Cluj-Napoca and others

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén