Mum, I want to be a Groovy
full-stack developer
IVÁN LÓPEZ
@ilopmar
Hello!
I am Iván López
@ilopmar
http://greachconf.com@madridgug
Thank you very much!
Q&A
Just kidding!
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
What's a full-stack developer?
Full-stack developer
Backend language
Javascript
HTML
Mobile App
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Polaromatic
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
1.
Demo
2.
Application flow
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
3.
Backend
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Polaromatic
▷ Spring Boot
▷ Core App
▷ Spring MVC
▷ Spring Integration Flow
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
Photo preprocessFile(File file) {
def pr = new PolaroidRequest(file)
this.preprocessFile(pr)
}
File service
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
File service
Photo preprocessFile(File file) {
def pr = new PolaroidRequest(file)
this.preprocessFile(pr)
} Photo preprocessFile(PolaroidRequest polaroidRequest) {
String outputFile = File.createTempFile("output", ".png").path
return new Photo(input: polaroidRequest.inputFile.absolutePath,
output: outputFile, text: polaroidRequest.text)
}
class ImageConverterService {
private static final String DEFAULT_CAPTION = "#LearningSpringBoot with Polaromaticn"
Random rnd = new Random()
Photo applyEffect(Photo photo) {
log.debug "Applying effect to file: ${photo.input}..."
def inputFile = photo.input
def outputFile = photo.output
double polaroidRotation = rnd.nextInt(6).toDouble()
String caption = photo.text ?: DEFAULT_CAPTION
def op = new IMOperation()
op.addImage(inputFile)
op.thumbnail(300, 300)
.set("caption", caption)
.gravity("center")
.pointsize(20)
.background("black")
.polaroid(rnd.nextBoolean() ? polaroidRotation : -polaroidRotation)
.addImage(outputFile)
def command = new ConvertCmd()
command.run(op)
photo
}
}
Image converter
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
FlickrDownloader
▷ Spring Boot CLI
▷ Download Flickr Interesting pictures
▷ Jsoup, GPars
▷ 55 lines of Groovy code
(microservice?)
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
private List extractPhotosFromFlickr() {
Document doc = Jsoup.connect(FLICKER_INTERESTING_URL).get()
Elements images = doc.select("img.pc_img")
def photos = images
.listIterator()
.collect { it.attr('src').replace('_m.jpg', '_b.jpg') }
photos
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
private File download(String url) {
def tempFile = File.createTempFile('flickr_downloader', '')
tempFile << url.toURL().bytes
tempFile
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2016-02-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2016-02-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2016-02-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2016-02-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2016-02-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2016-02-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2016-02-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2016-02-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
4.
Frontend
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Frontend
▷ MarkupTemplateEngine (HTML)
▷ Websockets
▷ Grooscript
HTML
yieldUnescaped '<!DOCTYPE html>'
html {
head {
title "Polaromatic"
link(rel: 'stylesheet', href: '/css/app.css')
link(rel: 'stylesheet', href: '/css/gh-fork-ribbon.css')
['webjars/sockjs-client/0.3.4-1/sockjs.min.js',
'webjars/stomp-websocket/2.3.1-1/stomp.min.js',
'webjars/jquery/2.1.3/jquery.min.js',
'webjars/handlebars/2.0.0-1/handlebars.min.js',
'js/Connection.js']
.each {
yieldUnescaped "<script src='$it'></script>"
}
}
}
HTML
body {
...
div(id: 'header') {
div(class: 'center') {
a(href: 'https://github.com/ilopmar/contest', target: 'blank') {
img(src: 'images/polaromatic-logo.png')
}
p('Polaromatic')
span('Powered by Spring Boot')
}
}
div(id: 'timeline', class: 'center')
}
script(id: 'photo-template', type: 'text/x-handlebars-template') {
div(class: 'photo-cover') {
div(class: 'photo', style: 'visibility:hidden; height:0') {
img(src: '{{image}}')
}
}
}
yieldUnescaped "<script>Connection().start()</script>"
}
Websockets
@Configuration
@EnableWebSocketMessageBroker
class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker '/notifications'
}
@Override
void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint('/polaromatic').withSockJS()
}
}
Websockets
class BrowserPushService {
@Autowired
SimpMessagingTemplate template
Photo pushToBrowser(Photo photo) {
log.debug "Pushing file to browser: ${photo.output}"
String imageB64 = new File(photo.output).bytes.encodeBase64().toString()
template.convertAndSend "/notifications/photo", imageB64
return photo
}
}
Websockets
class BrowserPushService {
@Autowired
SimpMessagingTemplate template
Photo pushToBrowser(Photo photo) {
log.debug "Pushing file to browser: ${photo.output}"
String imageB64 = new File(photo.output).bytes.encodeBase64().toString()
template.convertAndSend "/notifications/photo", imageB64
return photo
}
}
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
class Connection {
@GsNative
def initOn(source, path) {/*
var socket = new SockJS(path);
return [Handlebars.compile(source), Stomp.over(socket)];
*/}
def start() {
def source = $("#photo-template").html()
def (template, client) = initOn(source, '/polaromatic')
client.debug = null
client.connect(gs.toJavascript([:])) { ->
client.subscribe('/notifications/photo') { message ->
def context = [image: 'data:image/png;base64,' + message.body]
def html = template(context)
$("#timeline").prepend(html)
$("#timeline .photo:first-child img").on("load") {
$(this).parent().css(gs.toJavascript(display: 'none', visibility: 'visible', height: 'auto'))
$(this).parent().slideDown()
}
}
}
}
}
Grooscript (Javascript)
5.
Android
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Android App
▷ Disclaimer: I'm not an Android developer
▷ Lazybones template (@marioggar)
▷ Traits, @CompileStatic
▷ SwissKnife
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Android
trait Toastable {
@OnUIThread
void showToastMessage(String message) {
Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT)
toast.show()
}
}
Android
trait Toastable {
@OnUIThread
void showToastMessage(String message) {
Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT)
toast.show()
}
}
@CompileStatic
public class ShareActivity extends Activity implements Toastable {
...
showToastMessage(getString(R.string.share_ok_msg))
...
}
6.
Tests
Tests
▷ Spock Framework
▷ 0.7 for more than 4 years
▷ Now 1.0 (more than 1.5 years now)
▷ JUnit compatible (but way better)
Spock
class BrowserPushServiceSpec extends Specification {
void 'should push a converted photo to the browser'() {
given: 'a photo'
def output = File.createTempFile("output", "")
def photo = new Photo(output: output.path)
and: 'a mocked SimpMessagingTemplate'
def mockSimpMessagingTemplate = Mock(SimpMessagingTemplate)
and: 'the push service'
def browserPushService = new BrowserPushService(template: mockSimpMessagingTemplate)
when: 'pushing the photo to the browser'
browserPushService.pushToBrowser(photo)
then: 'the photo is pushed'
1 * mockSimpMessagingTemplate.convertAndSend('/notifications/photo', "")
}
}
7.
Build tool
Build tool
▷ Gradle
▷ Multiproject to build backend, documentation
and android
Gradle
subprojects {
buildscript {
repositories {
jcenter()
}
}
repositories {
jcenter()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.2.1'
}
include 'polaromatic-back'
include 'polaromatic-groid'
include 'polaromatic-docs'
build.gradle settings.gradle
8.
Documentation
Documentation
▷ Asciidoctor (FTW!)
▷ Gradle plugin
▷ Backends: html, epub, pdf,...
Asciidoctor
buildscript {
dependencies {
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2'
}
}
apply plugin: 'org.asciidoctor.convert'
asciidoctor {
sourceDir 'src/docs'
outputDir "${buildDir}/docs"
attributes 'source-highlighter': 'coderay',
toc : 'left',
icons : 'font'
}
Asciidoctor
[source,xml,indent=0]
.src/main/resources/resources.xml
----
include::{polaromaticBackResources}/resources.xml[tags=appFlow]
----
<1> Define the integration with the file system
<2> Preprocess the file received
<3> Apply the Polaroid effect
<4> Send the new photo to the browser using Websockets
<5> Update the metrics
<6> Delete all temporary files
Asciidoctor
[source,xml,indent=0]
.src/main/resources/resources.xml
----
include::{polaromaticBackResources}/resources.xml[tags=appFlow]
----
<1> Define the integration with the file system
<2> Preprocess the file received
<3> Apply the Polaroid effect
<4> Send the new photo to the browser using Websockets
<5> Update the metrics
<6> Delete all temporary files
<!-- tag::appFlow[] -->
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <!--1-->
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/> <!--2-->
<service-activator ref="imageConverterService" method="applyEffect"/> <!--3-->
<service-activator ref="browserPushService" method="pushToBrowser"/> <!--4-->
<service-activator ref="metricsService" method="updateMetrics"/> <!--5-->
<service-activator ref="fileService" method="deleteTempFiles"/> <!--6-->
</chain>
<!-- end::appFlow[]-->
Asciidoctor
9.
Summary
“
Groovy, groovy everywhere...
Thanks!
Any questions?
@ilopmar
lopez.ivan@gmail.com
https://github.com/ilopmar
Iván López
http://bit.ly/confoo-groovy

More Related Content

PDF
GR8Conf 2016 - Mum, I want to be a Groovy full-stack developer
PDF
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
PDF
Git Basics (Professionals)
PDF
Version Control with Git for Beginners
PDF
Git real slides
KEY
groovy & grails - lecture 13
PPTX
Using Git as your VCS with Bioconductor
PDF
.NET @ apache.org
GR8Conf 2016 - Mum, I want to be a Groovy full-stack developer
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Git Basics (Professionals)
Version Control with Git for Beginners
Git real slides
groovy & grails - lecture 13
Using Git as your VCS with Bioconductor
.NET @ apache.org

What's hot (20)

PDF
Analysing Github events with Neo4j
PDF
Git Real
PDF
分布式版本管理
PDF
Install Oracle 12c Golden Gate On Oracle Linux
PDF
The History of React-Hot-Loader
PDF
Configure Golden Gate Initial Load and Change Sync
PPTX
Everything you wanted to know about writing async, concurrent http apps in java
KEY
groovy & grails - lecture 10
PDF
Git the Docs: A fun, hands-on introduction to version control
PPTX
Presentation: Everything you wanted to know about writing async, high-concurr...
PDF
Reactive Programming with Rx
KEY
TDD of HTTP Clients With WebMock
PDF
Testing http calls with Webmock and VCR
PDF
What's new in Rails 4
PDF
Node.js cluster
KEY
Sane SQL Change Management with Sqitch
PPTX
Getting Started With Aura
KEY
Simple SQL Change Management with Sqitch
PDF
Mastering Maven 2.0 In 1 Hour V1.3
Analysing Github events with Neo4j
Git Real
分布式版本管理
Install Oracle 12c Golden Gate On Oracle Linux
The History of React-Hot-Loader
Configure Golden Gate Initial Load and Change Sync
Everything you wanted to know about writing async, concurrent http apps in java
groovy & grails - lecture 10
Git the Docs: A fun, hands-on introduction to version control
Presentation: Everything you wanted to know about writing async, high-concurr...
Reactive Programming with Rx
TDD of HTTP Clients With WebMock
Testing http calls with Webmock and VCR
What's new in Rails 4
Node.js cluster
Sane SQL Change Management with Sqitch
Getting Started With Aura
Simple SQL Change Management with Sqitch
Mastering Maven 2.0 In 1 Hour V1.3
Ad

Viewers also liked (18)

DOCX
PPTX
Top 8 sales marketing resume samples
PDF
Recommendation for Ng Bin Hong (1)
DOCX
Resume Mark Curtis 2015_05_07
PDF
PBO recommendation
DOC
Resume
DOC
YonghyunHwang_resume..
DOC
2015 resumeUpdated
PPTX
Top 8 professional services resume samples
PPTX
Top 8 tennis resume samples
PDF
Becoming a web developer full stack vs frontend-certificate of completion
DOC
Cv (english) Elmer Andrade
PPTX
Top 8 hris resume samples
DOC
Andrea Szabo cv-english 2
PDF
English CV
PDF
Planet Healthcare Services
PDF
Pratibha Vishwakarma Resume
Top 8 sales marketing resume samples
Recommendation for Ng Bin Hong (1)
Resume Mark Curtis 2015_05_07
PBO recommendation
Resume
YonghyunHwang_resume..
2015 resumeUpdated
Top 8 professional services resume samples
Top 8 tennis resume samples
Becoming a web developer full stack vs frontend-certificate of completion
Cv (english) Elmer Andrade
Top 8 hris resume samples
Andrea Szabo cv-english 2
English CV
Planet Healthcare Services
Pratibha Vishwakarma Resume
Ad

Similar to ConFoo 2016 - Mum, I want to be a Groovy full-stack developer (20)

PDF
Mum, I want to be a Groovy full-stack developer
PDF
HotPush with Ionic 2 and CodePush
PDF
Enabling Microservices @Orbitz - Velocity Conf 2015
PPTX
Automating Your Workflow with Gulp.js - php[world] 2016
PPTX
About Flink streaming
PPTX
2012 coscup - Build your PHP application on Heroku
ZIP
Mojolicious
ODP
(1) c sharp introduction_basics_dot_net
PDF
Shaping Clouds with Terraform
PPT
Implementing a Backup Catalog… on a Student Budget
PDF
Behaviour Driven Development con Behat & Drupal
PDF
Behaviour Driven Development con Behat & Drupal
PDF
Puppet Performance Profiling
PDF
release_python_day3_slides_201606.pdf
PPTX
Plone FSR
PPTX
Fastlane
PDF
Enabling Microservice @ Orbitz - GOTO Chicago 2016
PDF
Workshop: Introduction to Web Components & Polymer
PDF
PDF
Mum, I want to be a Groovy full-stack developer
HotPush with Ionic 2 and CodePush
Enabling Microservices @Orbitz - Velocity Conf 2015
Automating Your Workflow with Gulp.js - php[world] 2016
About Flink streaming
2012 coscup - Build your PHP application on Heroku
Mojolicious
(1) c sharp introduction_basics_dot_net
Shaping Clouds with Terraform
Implementing a Backup Catalog… on a Student Budget
Behaviour Driven Development con Behat & Drupal
Behaviour Driven Development con Behat & Drupal
Puppet Performance Profiling
release_python_day3_slides_201606.pdf
Plone FSR
Fastlane
Enabling Microservice @ Orbitz - GOTO Chicago 2016
Workshop: Introduction to Web Components & Polymer

More from Iván López Martín (20)

PDF
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
PDF
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
PDF
CommitConf 2024 - Spring Boot <3 Testcontainers
PDF
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
PDF
VMware - Testcontainers y Spring Boot
PDF
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
PDF
Codemotion Madrid 2023 - Testcontainers y Spring Boot
PDF
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
PDF
Construyendo un API REST con Spring Boot y GraalVM
PDF
jLove 2020 - Micronaut and graalvm: The power of AoT
PDF
Codemotion Madrid 2020 - Serverless con Micronaut
PDF
JConf Perú 2020 - ¡Micronaut en acción!
PDF
JConf Perú 2020 - Micronaut + GraalVM = <3
PDF
JConf México 2020 - Micronaut + GraalVM = <3
PDF
Developing Micronaut Applications With IntelliJ IDEA
PDF
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
PDF
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
PDF
Greach 2019 - Creating Micronaut Configurations
PDF
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
PDF
JavaDay Lviv 2019 - Micronaut in action!
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 Testcontainers
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
VMware - Testcontainers y Spring Boot
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Codemotion Madrid 2023 - Testcontainers y Spring Boot
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
Construyendo un API REST con Spring Boot y GraalVM
jLove 2020 - Micronaut and graalvm: The power of AoT
Codemotion Madrid 2020 - Serverless con Micronaut
JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3
Developing Micronaut Applications With IntelliJ IDEA
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Greach 2019 - Creating Micronaut Configurations
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
JavaDay Lviv 2019 - Micronaut in action!

Recently uploaded (20)

PDF
“A New Era of 3D Sensing: Transforming Industries and Creating Opportunities,...
PDF
OpenACC and Open Hackathons Monthly Highlights July 2025
PPTX
Custom Battery Pack Design Considerations for Performance and Safety
PPTX
Final SEM Unit 1 for mit wpu at pune .pptx
PDF
Credit Without Borders: AI and Financial Inclusion in Bangladesh
PPTX
MicrosoftCybserSecurityReferenceArchitecture-April-2025.pptx
PDF
1 - Historical Antecedents, Social Consideration.pdf
PPTX
TEXTILE technology diploma scope and career opportunities
PDF
CloudStack 4.21: First Look Webinar slides
PDF
Convolutional neural network based encoder-decoder for efficient real-time ob...
PPTX
Build Your First AI Agent with UiPath.pptx
PDF
Taming the Chaos: How to Turn Unstructured Data into Decisions
PDF
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
PDF
Enhancing plagiarism detection using data pre-processing and machine learning...
PDF
The influence of sentiment analysis in enhancing early warning system model f...
PPT
Geologic Time for studying geology for geologist
PDF
STKI Israel Market Study 2025 version august
PDF
sbt 2.0: go big (Scala Days 2025 edition)
PPTX
Microsoft Excel 365/2024 Beginner's training
PDF
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...
“A New Era of 3D Sensing: Transforming Industries and Creating Opportunities,...
OpenACC and Open Hackathons Monthly Highlights July 2025
Custom Battery Pack Design Considerations for Performance and Safety
Final SEM Unit 1 for mit wpu at pune .pptx
Credit Without Borders: AI and Financial Inclusion in Bangladesh
MicrosoftCybserSecurityReferenceArchitecture-April-2025.pptx
1 - Historical Antecedents, Social Consideration.pdf
TEXTILE technology diploma scope and career opportunities
CloudStack 4.21: First Look Webinar slides
Convolutional neural network based encoder-decoder for efficient real-time ob...
Build Your First AI Agent with UiPath.pptx
Taming the Chaos: How to Turn Unstructured Data into Decisions
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
Enhancing plagiarism detection using data pre-processing and machine learning...
The influence of sentiment analysis in enhancing early warning system model f...
Geologic Time for studying geology for geologist
STKI Israel Market Study 2025 version august
sbt 2.0: go big (Scala Days 2025 edition)
Microsoft Excel 365/2024 Beginner's training
Produktkatalog für HOBO Datenlogger, Wetterstationen, Sensoren, Software und ...

ConFoo 2016 - Mum, I want to be a Groovy full-stack developer

  • 1. Mum, I want to be a Groovy full-stack developer IVÁN LÓPEZ @ilopmar
  • 2. Hello! I am Iván López @ilopmar http://greachconf.com@madridgug
  • 3. Thank you very much! Q&A
  • 6. What's a full-stack developer?
  • 16. Polaromatic ▷ Spring Boot ▷ Core App ▷ Spring MVC ▷ Spring Integration Flow
  • 17. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow
  • 18. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow Photo preprocessFile(File file) { def pr = new PolaroidRequest(file) this.preprocessFile(pr) } File service
  • 19. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow File service Photo preprocessFile(File file) { def pr = new PolaroidRequest(file) this.preprocessFile(pr) } Photo preprocessFile(PolaroidRequest polaroidRequest) { String outputFile = File.createTempFile("output", ".png").path return new Photo(input: polaroidRequest.inputFile.absolutePath, output: outputFile, text: polaroidRequest.text) }
  • 20. class ImageConverterService { private static final String DEFAULT_CAPTION = "#LearningSpringBoot with Polaromaticn" Random rnd = new Random() Photo applyEffect(Photo photo) { log.debug "Applying effect to file: ${photo.input}..." def inputFile = photo.input def outputFile = photo.output double polaroidRotation = rnd.nextInt(6).toDouble() String caption = photo.text ?: DEFAULT_CAPTION def op = new IMOperation() op.addImage(inputFile) op.thumbnail(300, 300) .set("caption", caption) .gravity("center") .pointsize(20) .background("black") .polaroid(rnd.nextBoolean() ? polaroidRotation : -polaroidRotation) .addImage(outputFile) def command = new ConvertCmd() command.run(op) photo } } Image converter
  • 22. FlickrDownloader ▷ Spring Boot CLI ▷ Download Flickr Interesting pictures ▷ Jsoup, GPars ▷ 55 lines of Groovy code (microservice?)
  • 23. @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } FlickrDownloader
  • 24. @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } FlickrDownloader
  • 25. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } private List extractPhotosFromFlickr() { Document doc = Jsoup.connect(FLICKER_INTERESTING_URL).get() Elements images = doc.select("img.pc_img") def photos = images .listIterator() .collect { it.attr('src').replace('_m.jpg', '_b.jpg') } photos }
  • 26. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } private File download(String url) { def tempFile = File.createTempFile('flickr_downloader', '') tempFile << url.toURL().bytes tempFile }
  • 27. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } }
  • 28. FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2016-02-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2016-02-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2016-02-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 29. FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2016-02-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2016-02-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2016-02-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2016-02-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2016-02-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 32. Frontend ▷ MarkupTemplateEngine (HTML) ▷ Websockets ▷ Grooscript
  • 33. HTML yieldUnescaped '<!DOCTYPE html>' html { head { title "Polaromatic" link(rel: 'stylesheet', href: '/css/app.css') link(rel: 'stylesheet', href: '/css/gh-fork-ribbon.css') ['webjars/sockjs-client/0.3.4-1/sockjs.min.js', 'webjars/stomp-websocket/2.3.1-1/stomp.min.js', 'webjars/jquery/2.1.3/jquery.min.js', 'webjars/handlebars/2.0.0-1/handlebars.min.js', 'js/Connection.js'] .each { yieldUnescaped "<script src='$it'></script>" } } }
  • 34. HTML body { ... div(id: 'header') { div(class: 'center') { a(href: 'https://github.com/ilopmar/contest', target: 'blank') { img(src: 'images/polaromatic-logo.png') } p('Polaromatic') span('Powered by Spring Boot') } } div(id: 'timeline', class: 'center') } script(id: 'photo-template', type: 'text/x-handlebars-template') { div(class: 'photo-cover') { div(class: 'photo', style: 'visibility:hidden; height:0') { img(src: '{{image}}') } } } yieldUnescaped "<script>Connection().start()</script>" }
  • 35. Websockets @Configuration @EnableWebSocketMessageBroker class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker '/notifications' } @Override void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint('/polaromatic').withSockJS() } }
  • 36. Websockets class BrowserPushService { @Autowired SimpMessagingTemplate template Photo pushToBrowser(Photo photo) { log.debug "Pushing file to browser: ${photo.output}" String imageB64 = new File(photo.output).bytes.encodeBase64().toString() template.convertAndSend "/notifications/photo", imageB64 return photo } }
  • 37. Websockets class BrowserPushService { @Autowired SimpMessagingTemplate template Photo pushToBrowser(Photo photo) { log.debug "Pushing file to browser: ${photo.output}" String imageB64 = new File(photo.output).bytes.encodeBase64().toString() template.convertAndSend "/notifications/photo", imageB64 return photo } } <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain>
  • 38. class Connection { @GsNative def initOn(source, path) {/* var socket = new SockJS(path); return [Handlebars.compile(source), Stomp.over(socket)]; */} def start() { def source = $("#photo-template").html() def (template, client) = initOn(source, '/polaromatic') client.debug = null client.connect(gs.toJavascript([:])) { -> client.subscribe('/notifications/photo') { message -> def context = [image: 'data:image/png;base64,' + message.body] def html = template(context) $("#timeline").prepend(html) $("#timeline .photo:first-child img").on("load") { $(this).parent().css(gs.toJavascript(display: 'none', visibility: 'visible', height: 'auto')) $(this).parent().slideDown() } } } } } Grooscript (Javascript)
  • 41. Android App ▷ Disclaimer: I'm not an Android developer ▷ Lazybones template (@marioggar) ▷ Traits, @CompileStatic ▷ SwissKnife
  • 43. Android trait Toastable { @OnUIThread void showToastMessage(String message) { Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT) toast.show() } }
  • 44. Android trait Toastable { @OnUIThread void showToastMessage(String message) { Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT) toast.show() } } @CompileStatic public class ShareActivity extends Activity implements Toastable { ... showToastMessage(getString(R.string.share_ok_msg)) ... }
  • 46. Tests ▷ Spock Framework ▷ 0.7 for more than 4 years ▷ Now 1.0 (more than 1.5 years now) ▷ JUnit compatible (but way better)
  • 47. Spock class BrowserPushServiceSpec extends Specification { void 'should push a converted photo to the browser'() { given: 'a photo' def output = File.createTempFile("output", "") def photo = new Photo(output: output.path) and: 'a mocked SimpMessagingTemplate' def mockSimpMessagingTemplate = Mock(SimpMessagingTemplate) and: 'the push service' def browserPushService = new BrowserPushService(template: mockSimpMessagingTemplate) when: 'pushing the photo to the browser' browserPushService.pushToBrowser(photo) then: 'the photo is pushed' 1 * mockSimpMessagingTemplate.convertAndSend('/notifications/photo', "") } }
  • 49. Build tool ▷ Gradle ▷ Multiproject to build backend, documentation and android
  • 50. Gradle subprojects { buildscript { repositories { jcenter() } } repositories { jcenter() } } task wrapper(type: Wrapper) { gradleVersion = '2.2.1' } include 'polaromatic-back' include 'polaromatic-groid' include 'polaromatic-docs' build.gradle settings.gradle
  • 52. Documentation ▷ Asciidoctor (FTW!) ▷ Gradle plugin ▷ Backends: html, epub, pdf,...
  • 53. Asciidoctor buildscript { dependencies { classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' } } apply plugin: 'org.asciidoctor.convert' asciidoctor { sourceDir 'src/docs' outputDir "${buildDir}/docs" attributes 'source-highlighter': 'coderay', toc : 'left', icons : 'font' }
  • 54. Asciidoctor [source,xml,indent=0] .src/main/resources/resources.xml ---- include::{polaromaticBackResources}/resources.xml[tags=appFlow] ---- <1> Define the integration with the file system <2> Preprocess the file received <3> Apply the Polaroid effect <4> Send the new photo to the browser using Websockets <5> Update the metrics <6> Delete all temporary files
  • 55. Asciidoctor [source,xml,indent=0] .src/main/resources/resources.xml ---- include::{polaromaticBackResources}/resources.xml[tags=appFlow] ---- <1> Define the integration with the file system <2> Preprocess the file received <3> Apply the Polaroid effect <4> Send the new photo to the browser using Websockets <5> Update the metrics <6> Delete all temporary files <!-- tag::appFlow[] --> <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <!--1--> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <!--2--> <service-activator ref="imageConverterService" method="applyEffect"/> <!--3--> <service-activator ref="browserPushService" method="pushToBrowser"/> <!--4--> <service-activator ref="metricsService" method="updateMetrics"/> <!--5--> <service-activator ref="fileService" method="deleteTempFiles"/> <!--6--> </chain> <!-- end::appFlow[]-->