SlideShare a Scribd company logo
1 of 59
Download to read offline
Mum, I want to be a Groovy
full-stack developer
@ilopmarIván López
Hello!
I am Iván López
@ilopmar
http://greachconf.com@madridgug
Thank you very much!
Q&A
Just kidding!
Full-stack developer
Backend language
Javascript
HTML
Mobile App
Polaromatic
1.
Demo
2.
Application flow
3.
Backend
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
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
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
4.
Frontend
Fronted
▷ 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/lmivan/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
Android App
▷ Disclaimer: I'm not an Android developer
▷ Lazybones template (@marioggar)
▷ Traits, @CompileStatic
▷ SwissKnife
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 3 years
▷ Now 1.0
▷ JUnit compatible
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/lmivan
Iván López
http://kcy.me/22key

More Related Content

What's hot

From Spring Boot 2.2 to Spring Boot 2.3 #jsug
From Spring Boot 2.2 to Spring Boot 2.3 #jsugFrom Spring Boot 2.2 to Spring Boot 2.3 #jsug
From Spring Boot 2.2 to Spring Boot 2.3 #jsugToshiaki Maki
 
JavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good PartsJavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good PartsKonrad Malawski
 
Moving In: how to port your content from * to Drupal
Moving In: how to port your content from * to DrupalMoving In: how to port your content from * to Drupal
Moving In: how to port your content from * to DrupalEmma Jane Hogbin Westby
 
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Conference
 
The Future of Futures - A Talk About Java 8 CompletableFutures
The Future of Futures - A Talk About Java 8 CompletableFuturesThe Future of Futures - A Talk About Java 8 CompletableFutures
The Future of Futures - A Talk About Java 8 CompletableFuturesHaim Yadid
 
手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務Mu Chun Wang
 
分布式版本管理
分布式版本管理分布式版本管理
分布式版本管理jeffz
 
Angular Optimization Web Performance Meetup
Angular Optimization Web Performance MeetupAngular Optimization Web Performance Meetup
Angular Optimization Web Performance MeetupDavid Barreto
 
Riga Dev Day - Automated Android Continuous Integration
Riga Dev Day - Automated Android Continuous IntegrationRiga Dev Day - Automated Android Continuous Integration
Riga Dev Day - Automated Android Continuous IntegrationNicolas Fränkel
 
Presente e Futuro: Java EE.next()
Presente e Futuro: Java EE.next()Presente e Futuro: Java EE.next()
Presente e Futuro: Java EE.next()Bruno Borges
 
Ditching JQuery
Ditching JQueryDitching JQuery
Ditching JQueryhowlowck
 
Spring Boot and REST API
Spring Boot and REST APISpring Boot and REST API
Spring Boot and REST API07.pallav
 

What's hot (13)

From Spring Boot 2.2 to Spring Boot 2.3 #jsug
From Spring Boot 2.2 to Spring Boot 2.3 #jsugFrom Spring Boot 2.2 to Spring Boot 2.3 #jsug
From Spring Boot 2.2 to Spring Boot 2.3 #jsug
 
JavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good PartsJavaOne 2013: Java 8 - The Good Parts
JavaOne 2013: Java 8 - The Good Parts
 
Moving In: how to port your content from * to Drupal
Moving In: how to port your content from * to DrupalMoving In: how to port your content from * to Drupal
Moving In: how to port your content from * to Drupal
 
The JavaFX Ecosystem
The JavaFX EcosystemThe JavaFX Ecosystem
The JavaFX Ecosystem
 
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
 
The Future of Futures - A Talk About Java 8 CompletableFutures
The Future of Futures - A Talk About Java 8 CompletableFuturesThe Future of Futures - A Talk About Java 8 CompletableFutures
The Future of Futures - A Talk About Java 8 CompletableFutures
 
手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務手把手教你如何串接 Log 到各種網路服務
手把手教你如何串接 Log 到各種網路服務
 
分布式版本管理
分布式版本管理分布式版本管理
分布式版本管理
 
Angular Optimization Web Performance Meetup
Angular Optimization Web Performance MeetupAngular Optimization Web Performance Meetup
Angular Optimization Web Performance Meetup
 
Riga Dev Day - Automated Android Continuous Integration
Riga Dev Day - Automated Android Continuous IntegrationRiga Dev Day - Automated Android Continuous Integration
Riga Dev Day - Automated Android Continuous Integration
 
Presente e Futuro: Java EE.next()
Presente e Futuro: Java EE.next()Presente e Futuro: Java EE.next()
Presente e Futuro: Java EE.next()
 
Ditching JQuery
Ditching JQueryDitching JQuery
Ditching JQuery
 
Spring Boot and REST API
Spring Boot and REST APISpring Boot and REST API
Spring Boot and REST API
 

Similar to Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer

Mum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerMum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerGR8Conf
 
Analysing Github events with Neo4j
Analysing Github events with Neo4jAnalysing Github events with Neo4j
Analysing Github events with Neo4jChristophe Willemsen
 
About Flink streaming
About Flink streamingAbout Flink streaming
About Flink streaming용휘 김
 
.NET @ apache.org
 .NET @ apache.org .NET @ apache.org
.NET @ apache.orgTed Husted
 
Puppet Performance Profiling
Puppet Performance ProfilingPuppet Performance Profiling
Puppet Performance Profilingripienaar
 
Javascript Application Architecture with Backbone.JS
Javascript Application Architecture with Backbone.JSJavascript Application Architecture with Backbone.JS
Javascript Application Architecture with Backbone.JSMin Ming Lo
 
Enabling Microservices @Orbitz - Velocity Conf 2015
Enabling Microservices @Orbitz - Velocity Conf 2015Enabling Microservices @Orbitz - Velocity Conf 2015
Enabling Microservices @Orbitz - Velocity Conf 2015Steve Hoffman
 
(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_netNico Ludwig
 
Workshop: Introduction to Web Components & Polymer
Workshop: Introduction to Web Components & Polymer Workshop: Introduction to Web Components & Polymer
Workshop: Introduction to Web Components & Polymer John Riviello
 
Implementing a Backup Catalog… on a Student Budget
Implementing a Backup Catalog… on a Student BudgetImplementing a Backup Catalog… on a Student Budget
Implementing a Backup Catalog… on a Student BudgetRoy Zimmer
 
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014Pippin Williamson
 
release_python_day3_slides_201606.pdf
release_python_day3_slides_201606.pdfrelease_python_day3_slides_201606.pdf
release_python_day3_slides_201606.pdfPaul Yang
 
初心者Scala in f@n 第五回 sbt+giter8
初心者Scala in f@n 第五回 sbt+giter8初心者Scala in f@n 第五回 sbt+giter8
初心者Scala in f@n 第五回 sbt+giter8gak2223
 
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...Aaron Saunders
 
Plone FSR
Plone FSRPlone FSR
Plone FSRfulv
 
Shaping Clouds with Terraform
Shaping Clouds with TerraformShaping Clouds with Terraform
Shaping Clouds with TerraformMike Fowler
 
HotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushHotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushEvan Schultz
 
Database Change Management as a Service
Database Change Management as a ServiceDatabase Change Management as a Service
Database Change Management as a ServiceAndrew Solomon
 
MySQL User Conference 2009: Python and MySQL
MySQL User Conference 2009: Python and MySQLMySQL User Conference 2009: Python and MySQL
MySQL User Conference 2009: Python and MySQLTed Leung
 

Similar to Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer (20)

Mum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerMum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developer
 
groovy & grails - lecture 13
groovy & grails - lecture 13groovy & grails - lecture 13
groovy & grails - lecture 13
 
Analysing Github events with Neo4j
Analysing Github events with Neo4jAnalysing Github events with Neo4j
Analysing Github events with Neo4j
 
About Flink streaming
About Flink streamingAbout Flink streaming
About Flink streaming
 
.NET @ apache.org
 .NET @ apache.org .NET @ apache.org
.NET @ apache.org
 
Puppet Performance Profiling
Puppet Performance ProfilingPuppet Performance Profiling
Puppet Performance Profiling
 
Javascript Application Architecture with Backbone.JS
Javascript Application Architecture with Backbone.JSJavascript Application Architecture with Backbone.JS
Javascript Application Architecture with Backbone.JS
 
Enabling Microservices @Orbitz - Velocity Conf 2015
Enabling Microservices @Orbitz - Velocity Conf 2015Enabling Microservices @Orbitz - Velocity Conf 2015
Enabling Microservices @Orbitz - Velocity Conf 2015
 
(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net
 
Workshop: Introduction to Web Components & Polymer
Workshop: Introduction to Web Components & Polymer Workshop: Introduction to Web Components & Polymer
Workshop: Introduction to Web Components & Polymer
 
Implementing a Backup Catalog… on a Student Budget
Implementing a Backup Catalog… on a Student BudgetImplementing a Backup Catalog… on a Student Budget
Implementing a Backup Catalog… on a Student Budget
 
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014
Writing Your Own WordPress Plugins - WordCamp Kansas City, 2014
 
release_python_day3_slides_201606.pdf
release_python_day3_slides_201606.pdfrelease_python_day3_slides_201606.pdf
release_python_day3_slides_201606.pdf
 
初心者Scala in f@n 第五回 sbt+giter8
初心者Scala in f@n 第五回 sbt+giter8初心者Scala in f@n 第五回 sbt+giter8
初心者Scala in f@n 第五回 sbt+giter8
 
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...
Getting Started with Appcelerator Alloy - Cross Platform Mobile Development -...
 
Plone FSR
Plone FSRPlone FSR
Plone FSR
 
Shaping Clouds with Terraform
Shaping Clouds with TerraformShaping Clouds with Terraform
Shaping Clouds with Terraform
 
HotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePushHotPush with Ionic 2 and CodePush
HotPush with Ionic 2 and CodePush
 
Database Change Management as a Service
Database Change Management as a ServiceDatabase Change Management as a Service
Database Change Management as a Service
 
MySQL User Conference 2009: Python and MySQL
MySQL User Conference 2009: Python and MySQLMySQL User Conference 2009: Python and MySQL
MySQL User Conference 2009: Python and MySQL
 

More from Iván López Martín

CommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 TestcontainersCommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 TestcontainersIván López Martín
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdfVoxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdfIván López Martín
 
VMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring BootVMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring BootIván López Martín
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud GatewaySpring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud GatewayIván López Martín
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring BootCodemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring BootIván López Martín
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3Iván López Martín
 
Construyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVMConstruyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVMIván López Martín
 
jLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoTjLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoTIván López Martín
 
Codemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con MicronautCodemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con MicronautIván López Martín
 
JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!Iván López Martín
 
JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3Iván López Martín
 
JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3Iván López Martín
 
Developing Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEADeveloping Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEAIván López Martín
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfectaCommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfectaIván López Martín
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!Iván López Martín
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsIván López Martín
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet youVoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet youIván López Martín
 
JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!Iván López Martín
 
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerteCrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerteIván López Martín
 
Madrid-GUG - ¡Micronaut en acción!
Madrid-GUG - ¡Micronaut en acción!Madrid-GUG - ¡Micronaut en acción!
Madrid-GUG - ¡Micronaut en acción!Iván López Martín
 

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

CommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 TestcontainersCommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 Testcontainers
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdfVoxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
 
VMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring BootVMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring Boot
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud GatewaySpring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring BootCodemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring Boot
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
 
Construyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVMConstruyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVM
 
jLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoTjLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoT
 
Codemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con MicronautCodemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con Micronaut
 
JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!
 
JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3
 
JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3
 
Developing Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEADeveloping Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEA
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfectaCommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet youVoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
 
JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!
 
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerteCrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
 
Madrid-GUG - ¡Micronaut en acción!
Madrid-GUG - ¡Micronaut en acción!Madrid-GUG - ¡Micronaut en acción!
Madrid-GUG - ¡Micronaut en acción!
 

Recently uploaded

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Mark Simos
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demoHarshalMandlekar2
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfLoriGlavin3
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024BookNet Canada
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 

Recently uploaded (20)

Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
Tampa BSides - Chef's Tour of Microsoft Security Adoption Framework (SAF)
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Sample pptx for embedding into website for demo
Sample pptx for embedding into website for demoSample pptx for embedding into website for demo
Sample pptx for embedding into website for demo
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
Moving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdfMoving Beyond Passwords: FIDO Paris Seminar.pdf
Moving Beyond Passwords: FIDO Paris Seminar.pdf
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
New from BookNet Canada for 2024: Loan Stars - Tech Forum 2024
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 

Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer

  • 1. Mum, I want to be a Groovy full-stack developer @ilopmarIván López
  • 2. Hello! I am Iván López @ilopmar http://greachconf.com@madridgug
  • 3. Thank you very much! Q&A
  • 5.
  • 6.
  • 8.
  • 10.
  • 13.
  • 15.
  • 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
  • 21.
  • 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 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 29. FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 31.
  • 32. Fronted ▷ 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/lmivan/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)
  • 40.
  • 41. Android App ▷ Disclaimer: I'm not an Android developer ▷ Lazybones template (@marioggar) ▷ Traits, @CompileStatic ▷ SwissKnife
  • 42.
  • 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 3 years ▷ Now 1.0 ▷ JUnit compatible
  • 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[]-->