Advertisement
Guest User

Untitled

a guest
Apr 3rd, 2019
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 15.22 KB | None | 0 0
  1. Migrating applications from a well grounded framework to a completely new framework with just a few days after its public release, sounds craziness, right? The first questions I did to myself was "Why should I do that?", "is this new framework stable?", "What would be the gain?", well, a lot of different question will came up, but the most important in my opinion is, "Why?".To help me answering that question, I started to thinking about my application's performance, in this specific case, the bootstrap time, and asked myself "Am I happy with the actual time my application takes to startup?" The answer was "No, I am not.". Nowadays it became some of the most important metric to be considered when working with Microservices, mainly with Serverless architecture.The objective of this post is provide, at least, a point of reference for a basic migration of an existing Application to Quarkus. For this reason I'll save a few lines and focus most on the migration part.But, before proceed I strongly suggest you (if you don't know Quarkus yet) to read this article and visit the Quarkus homepage: https://quarkus.io to learn more about this super awesome framework.A little BackgroundIn this article, I'll try to illustrate all the changes, or at least the most important changes I had to make on my existing Application to make it run fine with Quarkus.The app used, is a Telegram API + Bot with several plugins that I've using as a "learn space", where I can use to try new things, frameworks, etc. So, please don't expect to see a very fancy or only best practices in this Application that will be used during this article.When this API was created, we decided to create a Telegram Java EE API so we could run it in any Application Server or framework that implements all the EE 7 Standards. The main reason was the possibility to use cool stuff like CDI, JPA and Rest. It was designed for WildFly Swarm and subsequently Thorntail. Since then there were no framework changes. Once Quarkus was released I decided to take a further look on it and the first impressions were amazing, and it supports all the dependencies that I had initially, and the most important, the possibility to create native images with GraalVM , so, I though, why not try to use it?Of course, a migration like this is not too easy, mainly if you are only aiming the native image.First Step TakenWhen I started to migrate the Application, I divided the work in smaller sections, which are:DependenciesDivide and Conquer (migrate modules, one by one)Code changesDependencies reviewDependencies ChangesWell, as you can imagine, the very first thing I did was completely remove Thorntail dependencies, in my case, the BOM from parent pom and the dependencies for every module.Changing from:<dependency>
  2.     <groupId>io.thorntail</groupId>
  3.     <artifactId>bom</artifactId>
  4.     <version>${version.io.thorntail}</version>
  5.     <scope>import</scope>
  6.     <type>pom</type>
  7. </dependency>To:<dependency>
  8.     <groupId>io.quarkus</groupId>
  9.     <artifactId>quarkus-bom</artifactId>
  10.     <version>${io.quarkus.version}</version>
  11.     <type>pom</type>
  12.     <scope>import</scope>
  13. </dependency>As second part, I also had to remove the javaee-api dependency, mostly used for CDI purposes and replace by the quarkus-arc, one of the Core libraries which provides CDI dependency injection, so, I did remove it from the parent pom and did big replace on every pom.xml:<dependency>
  14. - <groupId>javax</groupId>
  15. - <artifactId>javaee-api</artifactId>
  16. + <groupId>io.quarkus</groupId>
  17. + <artifactId>quarkus-arc</artifactId>
  18. + <scope>provided</scope>
  19. </dependency>Also, below you can find a few more dependencies that I had to change:io.thorntail:jaxrs -> io.quarkus:quarkus-resteasyio.thorntail:jpa -> io.quarkus:quarkus-hibernate-ormdata validation -> io.quarkus:quarkus-hibernate-validator.com.h2database -> io.quarkus:quarkus-jdbc-h2 (Quarkus already have some jdbc extensions, example H2, MariaDB and PostgreSQL, for oracle there is a good start point here.)On the project hierarchy this is the module that produces the runnable jar and is here that we need to move the Thorntail maven plugin out and add the new Quarkus maven plugin. This is a very important step, without it you will not be able to use Quarkus.One point of attention is, when working with a multi module project, you need to pay attention to the Quarkus dependencies, for example, in case of some of you modules exposes a Rest endpoint, in this case you'll need to use the quarkus-resteasy extension, but note that, in order to enable this extension, this dependency also needs to be declared on the main buildable module. Example:<dependency>
  20.     <groupId>io.quarkus</groupId>
  21.     <artifactId>quarkus-resteasy</artifactId>
  22.     <scope>provided</scope>
  23. </dependency>
  24. To make sure the extensions were installed as expected, you can always verify the logs, Quarkus let's you know which extensions were installed, a similar line is printed when the app is ready:INFO [io.quarkus] Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, narayana-jta, resteasy, scheduler]At this point, is the time where the fun really begins, a lot of issues, missing dependencies, etc. the first time a execute mvn compile quarkus:dev or mvn clean package, don't panic and remember, divide and conquer!Divide and Conquer and Code changesAt the first moment I confess I got a little bit scared, well, that was the right time to take a breath, get a cup of coffee and get the things done!The approach I used to make the things easier was start to make the build work first on the smaller modules and then moving forward to accomplish the first goal, get a successful build! At this point I removed out every dependency from the main pom.xml in order to build only the CoreAPI and the runnable jar.After going through the dependencies, my IDE started to complain about unsatisfied dependencies, in this case there were a issue related to EJB annotations. As you can see here, I used the EJB annotations to execute tasks during the app startup using @Startup,  @PostConstruct and @PreDestroy annotations. A simple way to fix it would be just remove the EJB annotations, keep using the PostConstruct and add a CDI annotation, i.e. ApplicationScoped on class level. But Quarkus provides a fancy way to control the Application's lifecycle, there is the Startup and Shutdown events that can be observed, like this example:@ApplicationScoped
  25. public class AppLifecycleBean {
  26.  
  27.     private static final Logger LOGGER = LoggerFactory.getLogger("ListenerBean");
  28.  
  29.     void onStart(@Observes StartupEvent ev) {               (1)
  30.         LOGGER.info("The application is starting...");
  31.     }
  32.  
  33.     void onStop(@Observes ShutdownEvent ev) {               (2)
  34.         LOGGER.info("The application is stopping...");
  35.     }
  36.  
  37. }For more details about this specific functionality, please take a look on Quarkus' documentation.  This change were appliedThe second and one of the easiest part was the Core API, and some basic changes to configure CDI. On the previous version, I was using the Service Provider Implementation and discovering new providers through Service Provider mechanism. With Quarkus I could completely remove the Service Provider files and replace it by the beans.xml, in my case,  all modules that uses CDI had to have the beans.xml file added as well so Quarkus can properly discovery CDI beans. Once it is done, I could move forward and try to build the CoreAPI. If you face a issue, like the example below, the first place you might want to look, is on the dependencies listed in the issue have the beans.xml:Unsatisfied dependency for type java.lang.String and qualifiers [@Default]One interesting issue I had to fix was a CDI Circular dependency where I was injecting ClassA and ClassB but ClassB already was Injecting ClassA, I am not sure why Thorntail was accepting it, but Quarkus is very strict.At this point, I got a successfully build with only the CoreAPI which requires some System Properties in order to correctly configure the Application, if the required properties are not set, the App will fail to start. Previously the API was expecting only for System Properties, with Quarkus this configuration was improved by also reading the microprofile-config.properties. Once the configuration of the app properties is done, I moved to the plugins.The first plugins I moved to Quarkus were the simplest one that does not requires Persistence or the Cache layer, for example, this simple ping plugin.One point of attention is the usage of private members, if you got a similar message as the below you will have to change the access modifiers, or if you really think that the acceess modifier needs to be private, you might want to use package-private instead:Found unrecommended usage of private members (use package-private instead) in application beansWow, at this point I already have my App running with the first plugin on Quarkus:INFO [it.reb.reb.Startup] (main) The application is starting...INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.359s.INFO [io.quarkus] (main) Installed features: [cdi]As most of the logging stuff of my app are under the FINE log level, I would like to configure the logging to print only debug messages that belongs to my app, to achieve this I had to add some logging configurations on the microprofile-config.properties, just like the example below:# DEBUG console loggingquarkus.log.console.enable=truequarkus.log.console.format=%d{HH:mm:ss} %-5p [%c] %s%e%nquarkus.log.console.level=TRACE# TRACE file loggingquarkus.log.file.enable=truequarkus.log.file.path=/tmp/quarkus.logquarkus.log.file.level=TRACEquarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n# custom loggersquarkus.log.category."it.rebase".level=TRACESchedulerSome of the plugins was using the EJB timer, in order to take advantage of everything that Quarkus provides, I used the quarkus-scheduler extension (Quartz under the hood), which also works with Annotations, and to be honest, its usage is very simple, i,e:@Scheduled(every = "1800s", delay = 30)and its dependency:<dependency>
  38.     <groupId>io.quarkus</groupId>
  39.     <artifactId>quarkus-scheduler</artifactId>
  40.     <scope>provided</scope>
  41. </dependency>Cache - InfinispanMy App was using the Infinispan embedded cache where I was using specific Qualifiers and Producers to configure a different cache for different purposes and where I could just Inject a specific cache according to my plugin needs. When I built the cache module I got this issue:[error]: Build step io.quarkus.arc.deployment.ArcAnnotationProcessor#build threw an exception: javax.enterprise.inject.spi.DefinitionException: Interceptor has no bindings: org.infinispan.jcache.annotation.CachePutInterceptorwhich was fixed by updating the Infinispan dependencies:Bump version from 9.1.1.Final to 9.4.9.FinalUpdate dependency from org.infinispan:infinispan-embedded to org.infinispan:infinispan-cdi-embeddedFrom this point I got some CDI issues like:Unsatisfied dependency for type org.infinispan.manager.EmbeddedCacheManager#defaultCacheContainerUnsatisfied dependency for type org.infinispan.cdi.embedded.InfinispanExtensionEmbedded#infinispanExtensionUnsatisfied dependency for type org.infinispan.Cache<java.lang.String, java.lang.String> and qualifiers [@Default]Basically the issue is telling me that there is no Producer for the methods above, to fix that I had to manually create producers to satisfy the issues mentioned, to start I modified the default cache by removing the old cache configuration that looks like:@Produces@ConfigureCache("default-cache")@DefaultCachepublic Configuration specialCacheCfg(InjectionPoint injectionPoint) {...}and add the new cache configuration:private DefaultCacheManager defaultCacheManager;
  42.  
  43. @Produces
  44. @DefaultCache
  45. public Cache<String, String> returnDefaultCacheStringObject() {
  46.     return defaultCacheContainer().getCache();
  47. }
  48.  
  49. @Produces
  50. public Configuration defaultCacheProducer() {
  51.     log.info("Configuring default-cache...");
  52.     return new ConfigurationBuilder()
  53.             .indexing()
  54.             .autoConfig(true)
  55.             .memory()
  56.             .size(1000)
  57.             .build();
  58. }
  59.  
  60. @Produces
  61. public EmbeddedCacheManager defaultCacheContainer() {
  62.     if (null == defaultCacheManager) {
  63.         GlobalConfiguration g = new GlobalConfigurationBuilder()
  64.                 .nonClusteredDefault()
  65.                 .defaultCacheName("default-cache")
  66.                 .globalJmxStatistics()
  67.                 .allowDuplicateDomains(false)
  68.                 .build();
  69.         defaultCacheManager = new DefaultCacheManager(g, defaultCacheProducer());
  70.     }
  71.     return defaultCacheManager;
  72. }
  73.  
  74. @Produces
  75. public InfinispanExtensionEmbedded defaultInfinispanExtensionEmbedded() {
  76.     return new InfinispanExtensionEmbedded();
  77. }Persistence ModuleBesides the dependency changes described earlier, on Thorntail to use database you would have to create a file called project-defaults.yaml under resources/ path and declare the database information there in addition of the persistence.xml file. With Quarkus it can be done by providing a file called application.properties  on the same path, like described here. Or can also provide all the database settings using System Properties on the command line when running the binary. So, the two old files were replaced by the application.properties, here is a example:quarkus.datasource.url: jdbc:h2:file:/opt/h2/rebot.db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
  78. quarkus.datasource.driver: org.h2.Driver
  79. quarkus.datasource.username: user
  80. quarkus.datasource.password: pwd
  81. quarkus.datasource.max-size: 8
  82. quarkus.datasource.min-size: 2
  83. # drop and create the database at startup (use `update` to only update the schema)
  84. quarkus.hibernate-orm.database.generation=drop-and-create
  85. # set to true to troubleshooting purposes
  86. quarkus.hibernate-orm.log.sql=falseAnother little change was a little tweak on the EntityManger Injection that has be changed from:@PersistenceContext(unitName = "rebotPU")
  87. private EntityManager em;to@Inject
  88. EntityManager em;Dependencies reviewCompletely remove resteasy dependencies and use quarkus-resteasy instead.After removing the resteasy a new issue start to happen:Caused by: java.lang.ClassNotFoundException: org.glassfish.jersey.client.JerseyClientBuilderTo solve this 3 new dependencies had to be added on the modules which was using the javax.ws.rs.client.ClientBuilder, the new dependencies are:org.glassfish.jersey.core:jersey-clientorg.glassfish.jersey.inject:jersey-hk2org.glassfish.jersey.media:jersey-media-json-jacksonIt definitely worth further investigation on this or drop the ClientBuilder usage.WARN: RESTEASY002145: NoClassDefFoundError: Unable to load builtin provider org.jboss.resteasy.plugins.providers.DataSourceProvider from jar:file:/home/spolti/.m2/repository/org/jboss/resteasy/resteasy-core/4.0.0.Beta8/resteasy-core-4.0.0.Beta8.jar!/META-INF/services/javax.ws.rs.ext.Providersjava.lang.NoClassDefFoundError: javax/activation/DataSourceWas fixed by adding the quarkus-hibernate-orm on the module that failed with this issue.I think I wrote down the most important points that came up during my experience while migrating my app to Quarkus. This were the first step, unfortunately I was not able to build the native binary because the Infinispan embedded cache dependency is not ready for it yet. definitely one of the next step would be look for an alternative for it, this will be a topic for a new article.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement