reactive programming for java developers – rossen stoyanchev – qcon

For more QCon posts, see my live blog table of contents.

General notes

  • Spring 5 has reactive features.
  • Maybe a third of the audience is doing something with reactive, a little more have read about it (that’s me) and a lot haven’t heard of it. Which is good given the title of this talk!
  • Moore’s law slowing down. No more free lunch from the hardware
  • Now apps more distributed/independent/cloudbased compared to 10 years ago
  • Now we expect latency
  • Thread now has to do more async things. Hit limits of scale. imperative logic more complicated.

Other approach

  • Async/non-blocking
  • Use very few threads. node.js has a single thread. Must be careful because blocking those few threads blocks the whole app
  • Reactive programming – think async. Not everything is a callback

Comparing styles

  • Imperative
    • call a method, get a result, do something with that result
  • Async
    • return a Future instead of a value. Don’t throw a checked exception because my happen in a different thread. They are asynchronous in that you can get a result later. But when you eventually call feature.get(), it throws different checked exceptions.
    • In Java 8, can return a CompletableFuture and call future.whenComplete((user, throwable) -> {})
    • If doing a findAll(), the future/completable future approach doesn’t give you a callback/return anything until all of them are ready. You can’t stream or could run out of memory.
    • Async results as stream – get one notification per data item and another for completion or error
  • Declarative
    • Focus on what, not how
    • Declare what should happen rather than focusing on callbacks
    • Java 8 stream API uses this style. It is meant for collections and pull based/usable once
    • Hot streams – latency sensitive streams, data need to be pushed for you
    • Cold streams – pull based

Reactive libraries

  • Stream like API
  • Can be used for hot/cold streams
  • Project Reactor – Similar to ReactiveX, easy to bridge to Java 8 streams. (ReactiveX is like XUnit – commonality for different languages)

Abstractions

  • Flux – sequence of 0 to n – equivalent of Java 8 stream – can convert to/from Java 8
  • Mono – sequence of 0 or 1 – can convert to/from CompletableFuture

Reactive streams spec

  • Back pressure – producers must not overwhelm consumers. Communicates if downstream isn’t ready for more items. Ex: a slow client like a mobile device isnt able to handle more data and server isn’t blocking
  • Small API – only 4 interfaces
  • Rules
  • Interoperability across reactive components so can compose chain

Java 9

  • Reactive streams included (the four interfaces)
    • Publisher
    • Subscriber
    • Subscription
    • Processor
  • Use a reactive library. Don’t implement the four interfaces yourself
  • subscribe() triggers the flow of data
  • “push” was missing until now. Want in JDK to have foundation
  • Classes
    • java.util.concurrent.Flow – the four interfaces
    • SubmissionPublisher – bridge to reactie streams
    • Tie-ins to CompletableFuture and Stream

Reactor

  • GA release scheduled for July
  • Currently called 2.5. Might changed to 3.0

Reactive Spring MVC

  • Apps annotate controllers even now.
  • Return a Flux type.
  • Spring MVC itself needs to change a lot – called Spring Web Reactive
  • The servlet API assumes blocking. There are async workarounds. Servlet 4.0 might support reactive spring integration, but probably just the basics. Using own bridge to Servlet 3.1 in meantime.
  • Can still use Jetty/Tomcat. They are non-blocking behind the scenes.
  • Don’t need servlet container. Can use Netty.
  • HandlerMapping – change to return a Mono so can be non-blocking
  • Focusing on REST/microservices scenarios

Other reactive efforts

  • MongoDB – reactive driver
  • Couchbase – reactive driver
  • Thymeleaf – split templates into chunks and throttle to respect backpressure

applying java 8 idioms to existing code – trisha gee – qcon

For more QCon posts, see my live blog table of contents. Last year, she talked about creating new apps in Java 8. This year is about migrating to Java 8 for “legacy” code. She showed specific JetBrains IDEA features. I just took notes on the concepts that are IDE agnostic. Presentation will be at bit.ly/refJ8

Usage from audience poll

  • About half compile with Java 7
  • About half compile with Java 8
  • About a quarter (half of Java 8 people)  compile with Java 8, but apps still looks like Java 7

Why use Java 8

  • Performance improvements in common data structure
  • Fork/Join speed improvements
  • Changes to support concurrency
  • Fewer lines of code – less boilerplate
  • New solutions to problems – more elegant/easier to write code
  • Minimize errors – ex: Optional avoids NullPointerException

Before refactoring

  • Make sure have high test coverage
  • Focus on method level changes – tests should still work
  • Have performance tests too to ensure not negatively impacting system
  • Decide on goals – performance? clearer code? easier to write new code? easier to read new code? use lambdas because team used to it? developer morale?
  • Limit the scope – small incremental commits. Don’t make change that will affect whole code base.

Straightforward refactoring of lambda expressions

  • Predicate, Comparator and Runnable are common places where might have used anonymous inner classes and can switch to lambdas
  • IDEs suggest where can replace code with lambdas (SonarLint does as well)
  • Quick win – reduce old boilerplate
  • [She didn’t talk about this, but it will make your code coverage go down. Make sure your management doesn’t worry about that!]
  • Lose explicit type information when use lambda. Might want to extra to method to make it clearer and call the method from the lambda.

Abstract classes – candidates for lambdas

  • See if can use an interface (if single abstract method) so can start using lambdas
  • Tag with @FunctionalInterface to make clearer
  • Then look at implementers (especially anonymous inner classes) and see if can convert to lambda expressions

Conditional logging

  • if (debug) { log.debug() } pattern is a good candidate for logging
  • Often people forget to add that conditional so opportunity to defer evaluation for those too via search/replace. Also, protects against conditional not matching log level. if (debug)  { log.severe() }. Uh oh!
  • Use a default method on the existing logging interface that takes a supplier instead of a String and has the if check

Collections and Streams API

  • Turn for loops into collect() or forEach()
  • Remember you can call list.forEach() directly without going through a stream
  • If do something like get rid of initial list size, test performance before and after to make sure not a needed optimization
  • The automatic streams refactoring might not result in shorter code. Think about whether the surrounding code wants to be refactored as well. A more dramatic refactoring. Go back to later. Also, avoid refactoring if and else code since not  a simple filter.
  • Remember to include method references when converting code too
  • Watch for surrounding code. For example, if sorting a list, move into the the stream. Or if iterating over new list, add as a forEach instead of turning into a list.

Optional

  • Be careful. Refactor locally.
  • Easy to accidentally change something that requires changing a ton of code.

Performance

  • Lambdas expressions don’t necessarily perform better than anonymous inner classes. Similarly for streams.
  • Using lambdas to add conditional logging is a big quick win performance improvement.
  • Streams didn’t need the “initial size of list” optimization
  • Adding parallel() to stream operations sometimes performs better and sometimes worse. Simple operation often performs worse in parallel because of overhead of parallelizing.
  • Introducing Optionals increases cost.

Java 7 refactorings

  • Use static imports
  • Reudndant type in diamond operator for generics

sonar plugins and why i had to fork the sonar web plugin to add a rule

Last weekend, a few of the CodeRanch moderators were discussing a Trac item that we’d like to get rid of the I18N messages from our HTML. It’s there at all because open source JForum had it. We haven’t been updating it and have been using it inconsistently. Also, we do want to externalize the ones in Java which is why we can’t just get rid of the file wholesale – someone could add more references from HTML. I suggested that this would be a good thing to set up a SonarQube rule and volunteered to create the rule. This was a far harder and more interesting task than I expected it to be when I said I’d do it.
webProps

The web plugin scans HTML files and has a bunch of pre-defined rules and templates for them. You can set any file extensions you’d like so I had set “.html,.html,.ftl” in Administration > Configuration > Web to hit both our HTML and Freemarker code. Alternatively, you can set this in your Sonar properties file.

Looking through the rules and rule templates in the web plugin, there wasn’t one for what I wanted – plain String or regular expression search. Nuts! There are a few existing rules that do what I want, however I couldn’t use them:

Rule Why can’t use
Some Java packages or classes should not be used in JSP files In web plugin, but only for jsp/jsp files
Disallowed methods should not be allowed In Java plugin so only works for Java code
XPath rule Only works for XML files
The text plugin regular expression rule So close! This does exactly what I need. Except (see next section)

Great, the text plugin meets my needs. But alas. You can only associate a file extension with one plugin for each Sonar run. The HTML/Freemarker files are already considered web files.

So what’s wrong with the text plugin?

The text plugin has a rule called “Simple Regex Match”. When configured with the proper regular expression, it does exactly what I want. Except for one huge problem. In order to use it, I have to associate the text plugin with HTML/Freemarker files. This is a two step procedure:

  • In the browser, Administration > Configuration > Web – set the web extensions to “foo”. Leaving it blank is insufficient as it defaults to include HTML and Sonar does not let the same file extension be associated with multiple plugins.
  • In sonar-project.properties, add the line: sonar-text-plugin.file.suffixes=.htm,.html,.ftl

You also have to run sonar-runner twice. Once with the HTML/Freemarker files associated with the web plugin to get those results and again with them associate with the text plugin to get the rule I’m trying to write. This means you need to have the code show up as two projects within SonarQube. (otherwise Sonar will tell you the rule violations were fixed for whichever analysis you run last.)

Extending the web plugin

Ok. I clearly need to extend the web plugin so that I can have a regular expression rule associated with the web plugin. The docs say the web plugin doesn’t support extending it with Java or XPath rules as does this post. I tried anyway hoping this was out of date. I was able to get my new rule to show up in Sonar, but not to run. Everything I’d need to do in order to get it to run was considered a duplicate and prevented Sonar from even starting up. For example, “org.sonar.api.utils.SonarException: Can not add the same measure twice” or “duplicate keys not allowed”. So I concede this path is a non-starter. It would have been nice to have my custom rule in a separate Maven project rather than having to fork the existing one.

Forking the web plugin

Sigh. Last resort. Time to fork. This newsgroup post pointed to a commit that shows how to add a rule to the web plugin. I wanted to add a rule template rather than a rule, but the idea is the same. It wasn’t hard to make the change within the project. It’s just that forking isn’t good in the long run as I’ll have to keep re-merging. What I needed to change in my fork:

  1. Add custom rule template class to org.sonar.plugins.web.checks.coding package. The code was pretty similar to the rule for illegal spaces:
    package org.sonar.plugins.web.checks.coding;
    
    import java.io.IOException;
    import java.nio.charset.Charset;
    import java.util.List;
    
    import org.sonar.api.utils.SonarException;
    import org.sonar.check.Priority;
    import org.sonar.check.Rule;
    import org.sonar.check.RuleProperty;
    import org.sonar.plugins.web.checks.AbstractPageCheck;
    import org.sonar.plugins.web.checks.RuleTags;
    import org.sonar.plugins.web.node.Node;
    import org.sonar.plugins.web.visitor.CharsetAwareVisitor;
    import org.sonar.squidbridge.annotations.NoSqale;
    import org.sonar.squidbridge.annotations.RuleTemplate;
    
    import com.google.common.io.Files;
    
    @Rule(
      key = "RegularExpressionNotAllowedOnLineCheck", 
      name = "Regular expression \"regex\" not allowed on web page", 
      priority = Priority.MAJOR, tags = {
      RuleTags.CONVENTION })
    @RuleTemplate
    @NoSqale
    public final class RegularExpressionNotAllowedOnLineCheck extends AbstractPageCheck implements CharsetAwareVisitor {
    
    	@RuleProperty(
          key = "regex", 
          description = "Single line regular expression to prohibit on web pages")
    	public String regex = "";
    	
    	private Charset charset;
    
    	@Override
    	public void setCharset(Charset charset) {
    	   this.charset = charset;
    	}
    
    	@Override
    	public void startDocument(List<Node> nodes) {
    		List<String> lines;
    		try {
    			lines = Files.readLines(getWebSourceCode().inputFile().file(), charset);
    		} catch (IOException e) {
    			throw new SonarException(e);
    		}
    		for (int i = 0; i < lines.size(); i++) {
    			// only support matching text within a single line to facilitate identifying line number
    			if (lines.get(i).matches("^.*" + regex + ".*$")) {
    				createViolation(i + 1, "Replace all instances of regular expression: " + regex);
    			}
    		}
    	}
    
    }
    
  2. Add the rule template to org.sonar.plugins.web.rules.CheckClasses so the web plugin knows about.
  3. Create a new HTML file in the resources localization directory.
  4. Write/update tests. I like that the sonar-web-plugin project uses JUnit tests to enforce some of there conventions like rules ending in “Check”.

Where is the code?

I’ve placed my fork on Github. It’s 2.5-SNAPSHOT because I didn’t see any need to version it. I also created a pull request for the sonar-web-plugin in hopes they are interested in merging it. The plugin was last released a year ago so I imagine I’ll be using my fork for along the foreseeable future in any case. Which is fine. CodeRanch is a volunteer run website so no rules against using forked software.