Reactively talking to Cloud Foundry with Groovy

By Greg Turnquist

Greg is a member of the Spring team, an author of several books on Spring Boot, conference speaker, and the lead for Spring Data JPA.

January 17, 2017

I’ve been working on this Spinnaker thing for over a year. I’ve coded support so Spinnaker can make continuous deployments to Cloud Foundry. And the whole thing is written in Groovy. I recently upgraded to that I can now talk reactively to Cloud Foundry with Groovy.

And it’s been a nightmare.

Why?

Groovy is pretty darn wicked. Coding Spring Boot apps mixed with Spring MVC controllers in the terse language of Groovy is nothing short of gnarly. But it turns out there’s a couple things where Groovy actually gets in your way.

Reactor + Cloud Foundry

Want a taste? The code fragment below shows part of a flow used to look up Spinnaker-deployed apps in Cloud Foundry:

operations.applications()
  .list()
  .flatMap({ ApplicationSummary appSummary ->
    operations.applications()
      .getEnvironments(GetApplicationEnvironmentsRequest.builder()
        .name(appSummary.name)
        .build())
      .and(Mono.just(appSummary))
  })
  .log('mapAppToEnv')
  .filter(predicate({ ApplicationEnvironments environments, ApplicationSummary application ->
    environments?.userProvided?.containsKey(CloudFoundryConstants.LOAD_BALANCERS) ?: false
  } as Predicate2))
  .log('filterForLoadBalancers')
  .flatMap(function({ ApplicationEnvironments environments, ApplicationSummary application ->
    operations.applications()
      .get(GetApplicationRequest.builder()
        .name(application.name)
        .build())
      .and(Mono.just(environments))
  } as Function2))

This is the new and vastly improved Cloud Foundry Java SDK built on top of Project Reactor’s async, non-blocking constructs (Mono and Flux with their operations). Every function call is an async, non-blocking operation fed to the next function call when the results arrive.

What does this code do? It looks up a list of Cloud Foundry apps. Iterating over the list, it weeds anything that doesn’t have a LOAD_BALANCER environment variable, a tell for Spinnaker-deployed apps. Finally it looks up the detailed record for each application.

The heart of the issue

What’s nestled inside several of these “hops” in this flow is a tuple structure. In functional flows like where each hop gets a single return, we often need to pass along more than one piece of data to the next hop. It’s the side effect of not using the imperative style of building up a set of variables, but instead passing along the bits in each subsequent funtion call.

cf-java-client has TupleUtils, a collection of functions meant to pack and unpack data, hop to hop. It’s elegant and nicely overloaded to support up to eight items passed between hops.

And that’s where Groovy falls flat. Groovy has this nice feature where it can coerce objects. However, with all the overloading, Groovy gets lost and can’t tell which TupleUtils function to target.

So we must help it by coercing it into the right structure. See those “as Function2”  and “as Predicate2” calls? That helps Groovy figure out the type of lambda expression to slide things into.

And it’s dragging me down!

The solution

So I finally threw in the towel and converted this one class into pure Java.

Yes, I ditched hip and cool Groovy in favor of the old warhorse Java.

You see, when something is so dependent on every character being in the right place, we need all the static support from the IDE we can get. Never fear; I’m not dropping Groovy everywhere. Just this one class.

And here is where Groovy’s interoperability with Java shines. Change the suffix of one file. Make the changes I need. And both the IDE and the compiler is happy, giving me an operational chunk of code.

I had to rewrite a handful of collections, but it wasn’t the worse thing in the world. In half a day, I had successfully moved the code. And now as I’m working on another flow, the pain of Groovy’s need for coercion specification is no longer wreaking havoc.

Cheers!

 

2 Comments

  1. Jochen Theodorou

    One remark on your post. It is not that Groovy cannot do calls to functional interfaces using Groovy Closures as you did. It is more the problem, that dynamic Groovy imposes more restrictions here than Java, since we lack the static information at runtime. And since the method selection happens at runtime we simply do not have enough information to do the “proper” thing in a Java sense… something Java also not always does in this area btw. Anyway using @CompileStatic the problem could be solved, but I doubt the implementation is there yet. But I also have to say, that if it is only the arity and nothing else we could solve the case in dynamic Groovy as well, just did not get to implement this yet

    And then there is of course the “issue” of creating an API for Java or Groovy. We really try to solve all the Java cases in Groovy too, but sometimes… you know in a pure Groovy API you would not need the Tuple2-Tuple8 classes as well as the TupilUtils class itself and similiar for Consumer2-Consumer8. Those are helper classes. A solution to a problem you may not have in Groovy in that way at all. So if the API had been made for Groovy you have gone another way and then not have the problem in the first place. Example: Assuming you have x and y as parameters in which y is a varargs you could do: consumer.accept(x, *y) to get a call to accept, which uses an arity based on the number of elements in the array you gave. then a accept method with the different arities is no problem to be called. Of course you won’t have the compiler checking the arity for you then.

    You are the first to come up with a complaint about this limitation. When we developed the current logic we have been aware of this and wanted to develop it at a later point. I could now go on about the whole Pivotal story here (hint for others: Pivotal did employ the majority of all paid Groovy, Spring and Cloud Foundry developers). Given time and money I see no reason why this situation could not be improved, given the above scope. Since both are missing at the moment nothing will happen right now – well..nothing but discussion.

    Reply
    • Greg Turnquist

      Thanks for stopping by Jochen. I agree that many factors would have swayed this in a different direction. I’m curious if we’ll see more of this with growth of lambda/functional APIs in Java. Nevertheless, groovy’ interoperability made it easy to slip in some antique Java and not waste time better spent on more features.

      Cheers!

      Reply

Submit a Comment

Your email address will not be published. Required fields are marked *