Saturday, October 27, 2018

Oracle Code One...A Conference I'll Never Forget

In the spring of 2018 when Oracle Code One was first announced, I was fairly certain that I would not be attending this year.  The main reason is because I normally only attended the JavaOne conference every couple of years, and I thought this would be an "off" year.  Since I hadn't planned on attending, I did not submit any talks.  I was happy to be asked to join the "Content Selection Committee" for Oracle Code One, as I had been a member of the selection committee for a number of JavaOne conferences in the past.  I enjoy being part of the committee, as it is great to see the diversity of talks submitted to the conference each year.

As the conference was about a month out, I received a surprise email congratulating me on being a 2018 Duke's Choice Award winner.  I was flabbergasted, and in complete disbelief.  So much so, that I replied to the email and asked if the sender had the correct person, and why I had been chosen to win this award.  I received an email response confirming that I indeed did win, and I had been chosen as the "Java EE" winner this year.  During the nomination period for the Duke's Choice Award, I had nominated a couple of projects...but I couldn't believe that someone had nominated me.  Still in disbelief, I knew that this had certainly changed things, and I was no doubt going to attend the conference this year.

This was going to be a different type of conference, as I was not speaking for the first time at this conference in years.  Since I had no talks to prepare for, I signed up for a time slot in the Hackergarten on Tuesday.  My friend and podcast colleague Freddy Guime purchased recording equipment so I could get some interviews for the Java PubHouse and OffHeap podcasts...so I now had plenty of things to do while at the conference!  Luckily, I was able to have my wife attend the conference with me so that she could be there as I received this surprising Duke's Choice award.  

I was very lucky to have the opportunity to attend the Java Champions summit on Saturday before the conference.  It was great to meet up with old friends again and listen to some amazing speakers at the Java Champions summit.  There were talks covering: the new Java release cadence, Jakarta EE, MicroProfile, Machine Learning, OpenJFX, Java and Containers, Women In Tech, JUG Evolution in Latin America, and JCP EC Update.  Excellent speakers, great friends, and very awesome...especially because this was my first "JavaOne" (okay...Code One but JavaOne always holds a place in my heart) as a Java Champion...being that I was inducted into the program in March of 2017.

I spent Sunday with my wife visiting the sites of San Francisco.  We were lucky to have such great weather to see places like Chinatown and Fisherman's Wharf.  We attended the "Java in the Cloud" event at the Thirsty Bear that evening, and it was great to see even more familiar faces that evening.  It really is the networking at conferences such as Oracle Code One that makes the event. 

Monday was full of great sessions, as I started off right with attending a talk from Adam Bien on Jakarta EE.  As per his standard, the talk contained some live coding and excellent examples of how to package Java EE applications into Docker containers.  I also attended a talk on "Serverless" from Ivar Grimstad, which focused on writing serverless functions and deploying to different cloud solutions.  After these talks, I attended a "JUG Leaders and User Group Appreciation Luncheon" with several other esteemed Java community members.  I had the honor of attending to represent the Chicago Java User's Group (CJUG).  After lunch, I attended a talk on MicroProfile by Emily Jiang and Antoine Sabot-Durand...excellent session!  Other sessions and an excellent Java Keynote concluded my Monday at the conference.  I then headed out with my wife to the annual JCP party.  This event has become one of my favorite gatherings whenever I attended the conference, and this year was no different...a great time was had by all who attended.

Tuesday was full of more great sessions, and it was also award day!  I worked the Hackergarten in the morning, where I finished up some licensing reviews for Apache NetBeans enterprise modules, and worked on testing Mojarra 2.3.9.  This was an especially great day because I had learned on Monday that the Apache NetBeans community was going to be receiving the Duke's Choice Award this year also...so I had the very rare opportunity to celebrate the Duke's Choice Award with the NetBeans Community, as well as for "Java EE".  Prior to the award ceremony in the afternoon, I visited the Java Hub with my wife and we explored the great vendor booths and fun activities such as the Java Escape Rooms.  As we gathered in the afternoon for the award ceremony, I saw and congratulated other members of the Apache NetBeans team.  

The first Duke's Choice Award was presented to Apache NetBeans, and it was surreal as we community members went on stage to receive the award as a community.  Many other great projects won the award as well.  MicroProfile was then presented the award, and to my surprise it was being presented to me!  This was the award that I was emailed about a month earlier...it was not for "Java EE", but rather, it was for MicroProfile!  I was shocked, surprised, and truly honored to have the privilege to accept the Duke's Choice Award on behalf of the MicroProfile community!!  Luckily enough, my friend David Heffelfinger was in attendance for the Apache NetBeans award, so I was able to get a photo with the Duke's Choice Award with him, as he is another member of the Java EE and MicroProfile community.  I was glad to have the great privilege of taking a photo together with some members of the MicroProfile community later that evening after the MicroProfile BoF session.

As mentioned, I am truly honored for having the opportunity to receive the award on behalf of the MicroProfile community.  This community has done amazing things in a very short time.  There are so many esteemed members of the Java community that contribute to the MicroProfile project...and I only wish that I had known that the award was for MicroProfile so that I could have invited them all to attend the award event to celebrate as a community.  Regardless...the MicroProfile community has made a tremendous impact to enterprise Java, and every member of the community should be proud of this win!!  The Dukes Choice Award is the most highly regarded award in the Java Ecosystem...and MicroProfile has achieved this award in such a short time...it is amazing!
Photo:  Josh Juneau and David Heffelfinger

I spent much of my Wednesday attending other sessions and recording interviews for the Java PubHouse and OffHeap podcasts.  I was so lucky to have well known members of the Java Community take a few moments out of their day to give me insight to their conference experience.  I wish I had more time to meet with many others in the community...but time flies so fast at the conference that it seemed as though I never had a spare minute!


I mentioned before that the Java Community makes the conferences worth attending.  That is what it is all about!  I met so many old friends and made many new acquaintances.  Not only did I meet up with many amazing Java luminaries, but I was lucky enough to finally meet up with a fellow Apress author Alex Jecan and Apress editor Jonathan Gennick.  It was also great to meet up with so many others such as Oracle Java Magazine editor in chief Andrew Binstock.  

I also had the unique opportunity to be with the Apache NetBeans community to accept the 2018 Duke's Choice Award, and I had the honor of accepting the 2018 Duke's Choice Award on behalf of the MicroProfile community!  This will certainly be a conference that I will never forget.  I want to thank the folks are Oracle, including those that run the Java Champions program, and my organization for helping to make this year's trip to Oracle Code One possible for me. Thanks Oracle Code One for an amazing conference...and I look forward to attending again and meeting up with all of my friends in the amazing Java community!


Photo (left-to-right): John Clingan, Ken Finnigan, Mark Little, Ian Robinson, Reza Rahman, Emily Jiang, Josh Juneau

Tuesday, September 18, 2018

Micronaut for Java EE/Jakarta EE Developers

There is a new microservices framework in town called Micronaut.  In this post, I'll discuss how I've ventured into working with the Micronaut framework from a Java EE/Jakarta EE perspective.  I am a Java EE developer, so developing microservices using solutions such as Eclipse MicroProfile are closer to my line of expertise, but Micronaut caught my attention since it offers the following abilities:

- Develop in Java, Groovy, or Kotlin
- Easy to test..fully integrated testing with Spock or JUnit
- Embedded Server & Compile Time HTTP Client
- Easy packaging for Docker
- Fast startup time, low memory consumption
- Fully Reactive

As an enterprise developer at heart, my first thought usually goes to the database, as the majority of applications that I author utilize an RDBMS.  I found the number of examples using Micronaut with an RDBMS to be few and far between, so I thought it may be useful for me to create another example for that use case.  In this example, I utilize PostgreSQL.  However, most other RDBMS are also supported.  This article is not meant to be a full explanation of installing Micronaut or utilizing all of the many Micronaut features.  Instead, it is a primer for those looking to get started utilizing Micronaut with a relational database...particularly geared towards those with some Java EE/Jakarta EE background.

In my particular case, I am interested in quickly spinning up Microservices that are fully testable, extensible, and efficient.  Although I can do this with MicroProfile or standard Java EE, I thought it would be interesting to learn something new and also have the ability to utilize Groovy or Kotlin.  I also wanted to put a Java EE/Jakarta EE spin on it...so I'm using JPA for working with the data.  Many of the Micronaut examples utilize Groovy and GORM for persistence...but I likely wouldn't be using that in any of my applications.

The example was developed using Apache NetBeans 9.0 and the Command Line Interface (CLI) that comes packaged with Micronaut.  This particular example was written against Micronaut 1.0.0.M4.  In this case, I kept it simple and utilized only a single, basic database table for persistence within a PostgreSQL database.

To begin, I created an app by utilizing the CLI by issuing the following command:


mn create-app org.acme.books --features hibernate-jpa,jdbc-tomcat

This simply creates a skeleton for my app within a directory named "books", and the Application.java main class will be placed within the org.acme.books package.  By default, there are basic features supported by an application, but in this case I've added support for the Tomcat connection pool.  This will be utilized when creating database connections via the Java Persistence API (JPA).  The default application is also generated with support for the Gradle build system.  Therefore, a build.gradle is created, and that is the file in which dependency management will take place.  Note that an application can also be generated utilizing the Apache Maven build system, but I had issues running Maven projects under Micronaut 1.0.0.M4...so I stuck with Gradle for this example.

If using Apache NetBeans 9.0, you can install the "Groovy and Grails" and "Gradle" plugins (currently available in the NetBeans 8.2 plugin center) to provide support for opening the project.  Once this is completed, the project can be opened within NetBeans and development can begin.  After installing the plugins and opening the project within Apache NetBeans, the completed project structure should look like that in the following figure:



To provide support for the PostgreSQL database, I added the dependencies to build.gradle:

compile group: 'org.postgresql', name: 'postgresql', version: '42.2.5'

Next, I opened up the application.yml file and added a datasource for the application.  This is the file that takes place of a persistence.xml within a traditional Java EE application.  Also, JPA support is added via this file, indicating which package includes the entity classes, as well as configuration of Hibernate.  Port 8080 is also set, as by default Micronaut will choose a random port on which to start the server.  The full sources of application.xml are as follows:

micronaut:
    application:
        name: books
                
#Uncomment to set server port
    server:
        port: 8080

---
datasources:
    default:
        url: jdbc:postgresql://localhost/postgres
        username: postgres
        password: yourpassword
        driverClassName: org.postgresql.Driver
        connectionTimeout: 4000
jpa:
  default:
    packages-to-scan:
        - 'org.acme.domain'
    properties:
      hibernate:
        hbm2ddl:
          auto: update
          show_sql: true

Now that the configuration is out of the way, I can get to the fun part...development.  In this example, I create a basic service allowing one to create, read, update, or delete records in the BOOK table.  The automatically generated Application class within the org.acme package, which starts the service.

package org.acme;

import io.micronaut.runtime.Micronaut;

public class Application {

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }
}

To begin development, create two packages within the application for organizing the source code.  First, create org.acme.domain, which will contain the entity class.  Next, create org.acme.book, which will contain the implementation classes.  Create a Book.java class within the org.acme.domain package, which will be the entity class containing a standard JPA mapping for the database.  In this case, note that I utilize java.time.LocalDate for the date fields, and I utilize a database sequence generator for population of the primary key.  The sources are as follows:


package org.acme.domain;

import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

/**
 * JPA Mappings for the BOOK database table.
 */
@Entity
@Table(name="BOOK")
public class Book {
    
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,
    generator="book_generator")
    @SequenceGenerator(name="book_generator",sequenceName="book_s", allocationSize=1)
    private Long id;
    
    @Column(name="PUBLISH_DATE")
    @NotNull
    private LocalDate publishDate;
    
    @Column(name="TITLE")
    @NotNull
    private String title;
    
    @Column(name="AUTHOR_FIRST")
    @NotNull
    private String authorFirst;
    
    @Column(name="AUTHOR_LAST")
    @NotNull
    private String authorLast;
    
    private Long pages;
    
    public Book(){}
    
    public Book(@NotNull Long id, @NotNull LocalDate publishDate, @NotNull String title, String authorFirst, String authorLast, Long pages){
        this.id = id;
        this.publishDate = publishDate;
        this.title = title;
        this.authorFirst = authorFirst;
        this.authorLast = authorLast;
        this.pages = pages;
    }

    public Book(@NotNull LocalDate publishDate, @NotNull String title, String authorFirst, String authorLast, Long pages){
        this.publishDate = publishDate;
        this.title = title;
        this.authorFirst = authorFirst;
        this.authorLast = authorLast;
        this.pages = pages;
    }

    /**
     * @return the id
     */
    public Long getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @return the publishDate
     */
    public LocalDate getPublishDate() {
        return publishDate;
    }

    /**
     * @param publishDate the publishDate to set
     */
    public void setPublishDate(LocalDate publishDate) {
        this.publishDate = publishDate;
    }

    /**
     * @return the title
     */
    public String getTitle() {
        return title;
    }

    /**
     * @param title the title to set
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * @return the authorFirst
     */
    public String getAuthorFirst() {
        return authorFirst;
    }

    /**
     * @param authorFirst the authorFirst to set
     */
    public void setAuthorFirst(String authorFirst) {
        this.authorFirst = authorFirst;
    }

    /**
     * @return the authorLast
     */
    public String getAuthorLast() {
        return authorLast;
    }

    /**
     * @param authorLast the authorLast to set
     */
    public void setAuthorLast(String authorLast) {
        this.authorLast = authorLast;
    }

    /**
     * @return the pages
     */
    public Long getPages() {
        return pages;
    }

    /**
     * @param pages the pages to set
     */
    public void setPages(Long pages) {
        this.pages = pages;
    }
   
    @Override
    public String toString() {
        return "Book{" +
            "id=" + id +
            ", publishDate='" + publishDate + '\'' +
            ", title='" + title + '\'' +
            ", authorFirst='" + authorFirst + '\'' +
            ", authorLast='" + authorLast + '\'' +
            ", pages='" + pages +
            '}';
    }
}

In a Micronaut application, HTTP requests and responses need to be encapsulated in Serializable classes for processing, and therefore it makes sense to generate some simple "Plain Old Java Objects" (POJOs) for encapsulating the data that will be used within database operations.  In the same org.acme.domain package, I created two such classes, BookSaveOperation.java and BookUpdateOperation.java.  These classes will define the fields required for passing data from the HTTP request to the controller class.  The sources for BookSaveOperation.java are as follows (see the GitHub repository for full sources):


package org.acme.domain;

import java.time.LocalDate;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
 *
 * @author Josh Juneau
 */
public class BookSaveOperation implements java.io.Serializable {
    
    
    @NotNull
    private LocalDate publishDate;
    
    @NotNull
    @NotBlank
    private String title;
    
    @NotNull
    @NotBlank
    private String authorFirst;
    
    @NotNull
    @NotBlank
    private String authorLast;
    
    private Long pages;
    
    public BookSaveOperation(){}
    
    public BookSaveOperation(LocalDate publishDate, String title,
                    String authorFirst, String authorLast, Long pages){
        this.publishDate = publishDate;
        this.title = title;
        this.authorFirst = authorFirst;
        this.authorLast = authorLast;
        this.pages = pages;
    }

   // ...
   // getters and setters
   // ...
}

The application business logic occurs within a class which is much like an EJB or DAO implementation, and the class must implement an interface that has defined each of the business logic methods.  In this case, I created an interface org.acme.book.BookRepository.java, and define a few standard operational methods:

package org.acme.book;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.acme.domain.Book;

/**
 *
 */
public interface BookRepository {
    Book save(LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages);

    Optional<Book> findById(Long id);

    void deleteById(Long id);

    List<Book> findAll();

    int update(Long id, LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages);
}

Next, implement that interface within a class entitled org.acme.book.BookRepositoryImpl.java, and annotate as a @Singleton.  Since this is the class which will implement business logic, inject a PersistenceContext, which provides the JPA EntityManager that will be used for performing database operations.  Simply implement each of the operations outlined within the BookRepository interface, marking each with @Transactional (io.micronaut.spring.tx.annotation.Transactional), implying read only for those methods that will not modify any data.  The sources for BookRepositoryImpl.java are as follows:


package org.acme.book;

import io.micronaut.configuration.hibernate.jpa.scope.CurrentSession;
import io.micronaut.spring.tx.annotation.Transactional;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.acme.domain.Book;

/**
 * Business logic for the service.
 */
@Singleton
public class BookRepositoryImpl implements BookRepository {
    @PersistenceContext
    private EntityManager entityManager;

    public BookRepositoryImpl(@CurrentSession EntityManager entityManager) {
        this.entityManager = entityManager;
    }
    
    @Override
    @Transactional
    public Book save(LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages) {
        Book book = new Book(publishDate, title, authorFirst, authorLast, pages);
        entityManager.persist(book);
        return book;
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<Book> findById(Long id) {
        return Optional.ofNullable(entityManager.find(Book.class, id));
    }

    @Transactional(readOnly = true)
    public List<Book> findAll() {
        return entityManager
                .createQuery("SELECT b FROM Book b", Book.class)
                .getResultList();
    }

    @Override
    @Transactional
    public int update(Long id, LocalDate publishDate, String title, String authorFirst, String authorLast, Long pages) {
        return entityManager.createQuery("UPDATE Book b SET publishDate = :publishDate, title = :title, " +
                "authorFirst = :authorFirst, authorLast = :authorLast, pages = :pages where id = :id")
                .setParameter("publishDate", publishDate)
                .setParameter("title", title)
                .setParameter("authorFirst", authorFirst)
                .setParameter("authorLast", authorLast)
                .setParameter("pages", pages)
                .setParameter("id", id)
                .executeUpdate();
    }

    @Override
    @Transactional
    public void deleteById(Long id) {
        findById(id).ifPresent(book -> entityManager.remove(book));
    }

}


In an effort to explain the Micronaut application infrastructure from a Java EE perspective, I'll compare the implementation with a simple JAX-RS application.  Micronaut utilizes io.micronaut.http.annotation.Controller classes to perform the request-response handling for a service.  This is much like a JAX-RS controller class, with a few slight differences.  This very much reminds me of the Eclipse Krazo project, or MVC 1.0 for Java EE.  For instance, instead of annotating methods with the JAX-RS annotations javax.ws.rs.GET, javax.ws.rs.POST, or javax.ws.rs.Path, Micronaut uses io.micronaut.http.annotation.Get and io.micronaut.http.annotation.Post, among others.  The URI path for each of the methods can be directly declared via the @Get, @Post, @Put, @Delete annotations.  Each controller class will implement the functionality for the service and handles the request-response life cycle.  The business logic for persistence (contained within the BookRepositoryImpl class) is injected into the controller class via the @Inject annotation or via constructor injection.  In the sources for this example, constructor injection is used.

package org.acme.book;

import org.acme.domain.Book;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Delete;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.Put;
import io.micronaut.validation.Validated;

import javax.validation.Valid;
import java.net.URI;
import java.util.List;
import org.acme.domain.BookSaveOperation;
import org.acme.domain.BookUpdateOperation;

@Validated
@Controller("/books")
public class BookController {

    protected final BookRepository bookRepository;

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

    @Get("/")
    public List<Book> list() {
        return bookRepository.findAll();
    }

    @Put("/")
    public HttpResponse update(@Body @Valid BookUpdateOperation operation) {

        bookRepository.update(operation.getId(), operation.getPublishDate(),
                operation.getTitle(), operation.getAuthorFirst(), operation.getAuthorLast(), operation.getPages());
        return HttpResponse.noContent().header(HttpHeaders.LOCATION, location(operation.getId()).getPath());

    }

    @Get("/{id}")
    Book show(Long id) {
        return bookRepository
                .findById(id)
                .orElse(null);
    }

    @Delete("/{id}")
    HttpResponse delete(Long id) {
        bookRepository.deleteById(id);
        return HttpResponse.noContent();
    }

    @Post("/")
    HttpResponse<Book> save(@Body @Valid BookSaveOperation operation) {

        Book book = bookRepository.save(operation.getPublishDate(), operation.getTitle(),
                operation.getAuthorFirst(), operation.getAuthorLast(), operation.getPages());
        return HttpResponse
                .created(book)
                .headers(headers -> headers.location(location(book)));
    }

    protected URI location(Book book) {
        return location(book.getId());
    }

    protected URI location(Long id) {
        return URI.create("/books/" + id);
    }

}

Testing the Application


Micronaut provides easy testing with Spock or JUnit and an embedded server...making it easy to create tests for each of the controllers.  In this case, I utilize JUnit to test the application.  I created a testing class within the test folder of the project named org.acme.BookControllerTest.

package org.acme;

import io.micronaut.context.ApplicationContext;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.HttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.acme.domain.Book;
import org.acme.domain.BookSaveOperation;
import org.acme.domain.BookUpdateOperation;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test cases for BookController
 */
public class BookControllerTest {

    private static EmbeddedServer server;
    private static HttpClient client;
    private  Book book;

    HttpRequest request;
    HttpResponse response;
    Long id;
    List<Long> bookIds = new ArrayList<>();

    @BeforeClass
    public static void setupServer() {
        server = ApplicationContext.run(EmbeddedServer.class);
        client = server.getApplicationContext().createBean(HttpClient.class, server.getURL());
    }

    @AfterClass
    public static void stopServer() {
        if (server != null) {
            server.stop();
        }
        if (client != null) {
            client.stop();
        }
    }

    @Test
    public void testInsertBooks() {

        request = HttpRequest.POST("/books", new BookSaveOperation(LocalDate.now(), "Java EE 8 Recipes", "Josh", "Juneau", new Long(750)));
        response = client.toBlocking().exchange(request);

        assertEquals(HttpStatus.CREATED, response.getStatus());

        request = HttpRequest.POST("/books", new BookSaveOperation(LocalDate.now(), "Java 9 Recipes", "Josh", "Juneau", new Long(600)));
        response = client.toBlocking().exchange(request);
        id = entityId(response, "/books/");

        assertEquals(HttpStatus.CREATED, response.getStatus());

       
    }

    @Test
    public void testBookRetrieve() {
        request = HttpRequest.GET("/books");
        List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));
        // Populate a book instance for later
        for(Book b:books){
            book = b;
        }
        assertEquals(2, books.size());

        

    }
    
    @Test
    public void testBookOperations() {
        request = HttpRequest.GET("/books");
        List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));
        // Populate a book instance for later
        for(Book b:books){
            book = b;
        }
        
        request = HttpRequest.PUT("/books/", new BookUpdateOperation(book.getId(),
                book.getPublishDate(),
                "Java 10 Recipes",
                book.getAuthorFirst(),
                book.getAuthorLast(),
                book.getPages()));

        response = client.toBlocking().exchange(request);

        assertEquals(HttpStatus.NO_CONTENT, response.getStatus());

        request = HttpRequest.GET("/books/" + book.getId());
        book = client.toBlocking().retrieve(request, Book.class);
        assertEquals("Java 10 Recipes", book.getTitle());

        testDelete();

    }
    
    
    public void testDelete(){
       request = HttpRequest.GET("/books");
        List<Book> books = client.toBlocking().retrieve(request, Argument.of(List.class, Book.class));
        // Populate a book instance for later
        for(Book b:books){
            request = HttpRequest.DELETE("/books/" + b.getId());
            response = client.toBlocking().exchange(request);
            assertEquals(HttpStatus.NO_CONTENT, response.getStatus());
        }
    }

    Long entityId(HttpResponse response, String path) {
        String value = response.header(HttpHeaders.LOCATION);
        if (value == null) {
            return null;
        }
        int index = value.indexOf(path);
        if (index != -1) {
            return Long.valueOf(value.substring(index + path.length()));
        }
        return null;
    }
}

Walk-Through of the Logic in Testing Class


In the method that runs @BeforeClass, the HTTP server and client are created.  Similarly, when the tests have finished executing, the method that is annotated with @AfterClass is invoked, which stops the server if it is running.

From within the textInsertBooks() method, two new book records are created by passing new BookSaveOperation objects populated with data to the service that is available via the "/books" path with the @Post designation.  In this case, the controller method  BookController.save() is invoked.  Taking a look at the save() method, you can see that the method simply passes the contents of BookSaveOperation to the BookRepository.save() business method (utilizing the interface), persisting the object.  In the end, an HttpResponse is returned.

The testBookRetrieve() method calls upon the service that is available via the "/books" path containing the @Get designation.  This, in-turn, calls upon the BookController.list() method, which performs a findAll() on the BookRepository, returning a List of Book objects.

The testBookOperations() method is responsible for performing updates to the records.  First, the list of Book objects is retrieved from the BookController, and then one of the books is updated via the BookController.update() method by populating a BookUpdateOperation object with the contents to be updated.

** Keep in mind, the BookSaveOperation.java and BookUpdateOperation.java objects are simply POJOs that are used to move the data

Lastly, the testDelete() method is invoked, which traverses the List of Book objects, calling upon the BookController.delete() method via the service call to the "/books" path and invoking the method designated as @Delete.

To execute the tests, simply right-click the project in NetBeans and choose "Test", or use the command line to invoke using the following command

./gradlew test

If the database table has not yet been created, then it will be generated for you.  Note that you may need to modify the database configuration within application.yml accordingly for your environment.

Running the Service

Micronaut is self-contained, allowing a service to be executed using the embedded server which is built on Netty.  This can be done by right-clicking the project in Apache NetBeans and selecting "Run".  You could also go to the command line and invoke using the following command:

./gradlew run

You should see the URL on which the server is hosted displayed within the output in the terminal or Apache NetBeans output panel.

Summary


As a Java EE developer, I have to say that Micronaut does have a different development approach.  It is not too much different than that of Spring Boot or Grails, but it is different enough that it took me some time to find my way around.  In the end, I find it an easy to use framework that has a lot of potential for quickly developing services or scheduled tasks, while still harnessing some of the concepts from Java EE/Jakarta EE.  

There are a number of features that I've not played much with yet, such as creating scheduled tasks via the Micronaut framework, and developing using Groovy or Kotlin, rather than Java.  I hope to follow up to this post in the future with more information for Java EE and Jakarta EE developers who are interested in beginning their journey with Micronaut.