« Back
in Spring Spring Boot Groovy Chat Wicket Vaadin PubNub Microservices Java read.

Real-Time Chat App with Server Side Push in ~200 Lines of Code.

Last night I happened to come across a link to Vaadin - an open source framework for rich single page web applications. I first heard about Vaadin about 6 to 7 years ago when my team at the time was evaluating different Java web presentation frameworks for a project. We looked at Spring MVC, Wicket, ICEFaces, Vaadin, and GWT. We built proof-of-concept applications using each of these frameworks. In the end, I decided on Wicket, though Vaadin came to a close second.

There were a lot of things I really liked about Wicket. I was impressed by the clean separation of markup and logic. Extending the framework was effortless and even enjoyable! Wicket's component-based architecture really felt like a true object-oriented web framework. The fact that I was able to build self-contained UI components - complete with logic, markup and style - and package them up in jar for re-use was pretty freaking cool.

Fast forward to 2016, and most web architectures have moved from stateful to stateless. The recent web projects I developed over these last few years have consisted of rich AngularJS-based front-ends, communicating with stateless backend REST services. And with the emergence of mobile apps, Docker containers, Microservices and Serverless architectures like AWS Lambda, there are plenty of reasons to believe that we will continue to move towards thin server architectures or stateless backend systems that are easy to scale and deploy in multiple high-availability zones.

However, this is not to say that server-side web presentation frameworks like Vaadin or Wicket are obsolete. Not all web applications are going to have thousands of concurrent users. Few web applications truly require high availability deployment regions. In most cases, "stateful" server-side presentation frameworks do the job just fine. They also come with many other advantages just to name a few:

  • Monoglot ( the same language for both client/server logic )
  • Rich, comprehensive, readily-available UI components
  • Flat learning curve and faster development
  • Simple Client/Server integration

You might be asking, "What does this have to do with the this post???"

Keep reading.


Spring Boot + Groovy + PubNub + Vaadin

Groovy is probably my favorite programming language right now. Writing Groovy code is like making sweet love to IntelliJ - just kidding. I am also a big proponent of the Spring IO Ecosystem. I recently implemented a Spring Boot Microservice at Tendril exposing some Data Science algorithms written in the R language as a REST service in Spring. I utilized the Renjin Project to execute R code in the JVM. The integration worked beautifully. With Spring Boot, I was able to launch a production-ready microservice in less than 2 weeks - especially with the help of Spring Boot Actuator.

While working on the Tendril's MyHome mobile application and APIs, we chose PubNub as the backbone of our real-time messaging infrastructure. I found PubNub to be very reliable, performant and very easy to work with overall. I've been thinking of other ways to integrate PubNub into some side projects.

So when I accidentally clicked on the Vaadin link last night, I was reminded of how easy it was to build generic web UI components using the framework. So I challenged myself - I bet I could write a very rudimentary but fully functional real-time application using Spring Boot, Groovy, PubNub and Vaadin with very few lines of code.

So here's what I came up with after about 2 hours of "play time". ( In fact, it took me longer to write this damn blog post )

First, install Spring Boot CLI

$ brew tap pivotal/tap
$ brew install springboot



Second, sign up for a PubNub account and create a sample application. Keep note of the publish keys, subscribe keys and secret keys.

Next, Create a ChatApp.groovy file with the following contents. You can download the entire script here.

  
@Theme("valo")
@SpringUI(path = "")
@Push
class ChatApp extends UI {

  private static AtomicInteger visitor = new AtomicInteger()
  private static final String CHANNEL = UUID.randomUUID().toString()

  @Lazy
  private TextField nameInput, chatInput

  @Lazy
  private VerticalLayout mainLayout, chatLayout

  @Lazy
  private HorizontalLayout footer

  @Lazy
  private Panel chatPanel = { new Panel(chatLayout) }()

  private SubscribeCallback subscribeCallback

  @Override
  protected void init(VaadinRequest request) {

    loadHistory()

    // Set up main layout
    mainLayout.setSizeFull()
    mainLayout.addComponent(chatPanel)
    mainLayout.setExpandRatio(chatPanel, 1)
    mainLayout.addComponent(footer)
    setContent(mainLayout)

    // Set up chat panel
    chatPanel.setSizeFull()

    // Set up chat input
    nameInput.setValue("Anonymous " + visitor.incrementAndGet())
    chatInput.focus()
    chatInput.setWidth("100%")
    chatInput.setImmediate(true)
    chatInput.addValueChangeListener(addNewChatLine as Property.ValueChangeListener)

    // Set up footer
    footer.setWidth("100%")
    footer.addComponent(nameInput)
    footer.addComponent(chatInput)
    footer.setExpandRatio(chatInput, 1)
    footer.addComponent(new Button("Send", addNewChatLine as Button.ClickListener))

    listen()
  }

  // Respond to Button Click or Enter Key
  def addNewChatLine = {
    if (chatInput.value?.trim()) {
      def chat = Chat.newInstance()
      chat.with {
        ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
        name = nameInput.value
        message = chatInput.value
      }
      publish(chat)
      chatInput.value = ""
    }
  }

  // Display chat message
  def logChat(Chat chat) {

    chat.ts = chat.ts ?: LocalDateTime.now()
        .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))

    def chatLine = new HorizontalLayout()
    chatLine.setWidth("100%")

    def timeLabel = new Label(chat.ts)
    timeLabel.setWidth("150px")
    chatLine.addComponent(timeLabel)

    def nameLabel = new Label(chat.name + ":")
    nameLabel.setWidth("150px")
    chatLine.addComponent(nameLabel)

    def messageLabel = new Label(chat.message)
    chatLine.addComponent(messageLabel)
    chatLine.setExpandRatio(messageLabel, 1)

    chatLayout.addComponent(chatLine)
    scrollIntoView(chatLine)
  }

  // Publish message to PubNub
  def publish = { Chat chat ->
    pubNub().publish()
        .message(chat)
        .channel(CHANNEL)
        .async({PNPublishResult result, PNStatus status -> /* do nothing */ })
  }

  // Listen to incoming messages
  def listen = {
    subscribeCallback = new SubscribeCallback() {
      @Override
      public void status(PubNub pubnub, PNStatus status) {}

      @Override
      public void message(PubNub pubnub, PNMessageResult message) {
        def chat = JsonSlurper.newInstance()
            .parseText(message.message.toString()) as Chat
        try{
          access {
            logChat(chat)
          }
        }
        catch (e){
          e.printStackTrace()
        }
      }

      @Override
      public void presence(PubNub pubnub, PNPresenceEventResult presence) {}
    }
    pubNub().addListener(subscribeCallback)
    pubNub().subscribe().channels([CHANNEL]).execute()
  }

  // Load the chat history
  def loadHistory = {
  pubNub().history()
    .channel(CHANNEL)
    .count(100) // how many items to fetch
    .async { PNHistoryResult result, PNStatus status ->
      def messages = result.messages?.collect {
        JsonSlurper.newInstance().parseText(it?.entry?.toString()) }
      access {
        messages?.each {
          logChat(it as Chat)
        }
      }
    }
}

  @Memoized
  static PubNub pubNub() {
    def pnConfiguration = new PNConfiguration()
    pnConfiguration.with {
      // Replace with your PubNub keys
      subscribeKey = 'xxxxxxxxxxxxxxxxx'
      publishKey = 'xxxxxxxxxxxxxxxxx'
      secretKey = 'xxxxxxxxxxxxxxxxx'
    }
    new PubNub(pnConfiguration)
  }

  // Clean up. Remove the message listener when UI instance is destroyed
  @Override
  public void detach() {
    pubNub().removeListener(subscribeCallback)
  }
}

class Chat {  
  String ts
  String name
  String message
}










To launch the application, simply run:

spring run ChatApp.groovy  

Then go to http://localhost:8080 to view the app.

Voilà! Now we have a simple chat application with a persistent backend, capable of replaying chat history with less than 200 lines of code.

Sweet!






comments powered by Disqus