Tu API ha muerto

larga vida a tu DSL

Jorge Aguilera

Presentación

  • @jagedn (Jorge Aguilera)

  • Extensiones de Asciidoctor (Puravida Extensions)

  • Gradle Plugins

  • Groovy (101 GroovyScript)

  • Groogle (Groovy+Google)

  • Disclaimer

Created by Jorge Aguilera / @jagedn

Agenda

  • DSL

    • qué es

    • DSL "famosos"

  • API vs DSL

  • DSL Groovy

  • Ejemplo práctico: Groogle

Tu API (email example)

API
public void sendMail(String from, String to, String subject, String body) {
      Properties properties = System.getProperties();
      properties.setProperty("mail.smtp.host", host);
      Session session = Session.getDefaultInstance(properties);
      MimeMessage message = new MimeMessage(session);
      message.setFrom(new InternetAddress(from));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
      message.setSubject(subject);
      message.setText(body);
      Transport.send(message);
}

Consume API

Consume
Excel excel = new Excel("/my/path/crm_campaing.xls");
Row row = excel.getRow(1);
email.sendMail( row.getCell(0),row.getCell(1), row.getCell(2),row.getCell(3));

DSL

DSLs are small languages, focused on a particular aspect of a software system. You can’t build a whole program with a DSL, but you often use multiple DSLs in a system mainly written in a general purpose language.
https://martinfowler.com/books/dsl.html
— Martin Fowler

DSL "famosos"

SQL

SELECT * FROM TABLE WHERE FIELD1 = 'STR' AND FIELD2 > 20

HTML

<html>
    <body>
        <div>
            <span>Hola</span>
        </div>
    </body>
</html>

Java "fluent"

Spring Integration (Builder style)
IntegrationFlows.from("example")
    .channel(this.inputChannel())
    .filter((Integer p) -> p > 0)
    .transform(Object::toString)
    .channel(MessageChannels.queue())
    .get();

Groovy Closures

Gradle
repositories {
    mavenCentral()
    jcenter()
    maven {
        url  "https://dl.bintray.com/puravida-software/repo"
    }
}

Inventado

Plot
plot {
    function "cos(sin(x))" and  "x*cos(e*x)" and "t^4/x"
        from (-3) incrementing 0.01 to 3
}
200

DSL everywhere

  • reglas de entrega de mercancia

  • definición de flujos de ayuda telefónica

  • generador imágenes png de funciones matemáticas

  • integración Google Drive con pipelines Jenkins

DSL en Groovy

  • características del propio lenguaje

  • Closure con @DelegatesTo

Propias de Groovy

turn(Directions.left).then(Directions.right);

take( new Pills(2) ).of( new Chloroquinine() ).after( new Hours(6) )

paint(wall).with(Color.red, Color.green).and( Color.yellow )

versus

turn left then right

take 2.pills of chloroquinine after 6.hours

paint wall with red, green and yellow

Closures

Closure cl = {
    println "my age is $age"
}

class A{              class B{
   int age = 2           String getAge(){"a string"}
}                     }

cl.delegate = new A()
cl()
cl.delegate = new B()
cl()

Email example

API
public void sendMail(String from, String to, String subject, String body) {
      Properties properties = System.getProperties();
      properties.setProperty("mail.smtp.host", host);
      Session session = Session.getDefaultInstance(properties);
      MimeMessage message = new MimeMessage(session);
      message.setFrom(new InternetAddress(from));
      message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
      message.setSubject(subject);
      message.setText(body);
      Transport.send(message);
}

Consume API

Consume
Excel excel = new Excel("/my/path/crm_campaing.xls");
Row row = excel.getRow(1);
email.sendMail( row.getCell(0),row.getCell(1), row.getCell(2),row.getCell(3));

Email DSL

DSL
email {
  from "from@example.com"  to "to@example.com"
  subject "Subject"
  body """
  Dear friend
  blablabala

  Regards
  """
}

Email DSL

class EmailSpec{
    String from, to, subject, body

    EmailSpec from(String from) {
        this.from = from
        this
    }

    EmailSpec to(String... to) { }

    EmailSpec subject(String subject) { }

    EmailSpec body(String subject) { }
}

Email DSL

class EmailBuilder{
    static EmailSpec email( @DelegatesTo(strategy=Closure.DELEGATE_ONLY,
                            value=EmailSpec) Closure cl) {
        def email = new EmailSpec()
        def code = cl.rehydrate(email, email, email)
        code.resolveStrategy = Closure.DELEGATE_ONLY
        code()
        sendEmail(email.from,email.to,email.subject,email.body)
    }
}

Groogle

Groogle es un proyecto abierto que ofrece un DSL para acceder a servicios de Google

https://puravida-software.gitlab.io/groogle

Google API’s

Conjunto de API’s que permiten el acceso a servicios de Google (Gmail, Drive, Sheet, y un largo etcetera)

Casi todas son REST (pero complejas) así que hay librerías Java, Python, etc

Librería y DSL

  • Groogle comenzó como librería para autentificación pero ha evolucionado a ofrecer DSL específicas en cada servicio, aunque pueden ser usadas en conjunto.

  • Uso directo en GroovyScripts

  • Groogle está implementado en Groovy pero se puede usar desde Java 8 mediante lambdaS (no está muy fino)

  • Descargar como dependencias desde jCenter

Motivación

Script para 101-groovy-scripts que accediera al Calendar de un usuario para hacer un gráfico usando sus eventos. (https://groovy-lang.gitlab.io/101-scripts/google/calendar.html)

Casos de Uso

  • Compartir información con clientes

  • Volcar una tabla a un Sheet compartido (o viceversa)

  • Gestionar eventos de un calendario de forma programada

  • Servicio REST de una hoja + ficheros en Drive

  • etc

Lenguages/Entornos

  • Groovy script

  • Java ( min 8)

  • Grails, Ratpack

  • Dockerizado

Subproyectos

  • Autentificación (OAuth y de servicio) [x]

  • Drive [x]

  • Sheet [x]

  • Calendar [x]

  • Chart (en beta y con futuro incierto) [ ]

  • Gmail [ ]

  • Team [ ]

  • siguiente según demanda [ ]

Autentificación

groogle-core

Crear una aplicación en la consola de Google junto con unas credenciales. Necesitamos descargar el json y poder acceder a él (como resource, file, etc)

Formas de identificarse

  • Autentificación OAuth2: el usuario selecciona la cuenta con la que trabajar

  • Cuenta de Servicio. Diferentes usuarios permiten acceder a alguno de sus recursos

Tips

Definir los roles que vamos a usar (Drive, Calendar, Sheet …​)

Se crea una carpeta $HOME/.credentials/${appname} donde guardar los tokens

API

Authenticate.java
public static void main(String[] args) {
  .......
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  .......
}

DSL

Se abre una ventana en un navegador y seleccionamos cuenta de usuario
login{
    applicationName 'groogle-example'
    withScopes DriveScopes.DRIVE, SheetsScopes.SPREADSHEETS
    usingCredentials '/client_secret.json'
    asService false
}

Drive

  • Buscar ficheros/carpetas en el Drive con filtros

  • Subir ficheros de nuestro local (y convertirlos automaticamente)

  • Bajar ficheros de Drive

Upload a file with Google

DriveSample.java
File fileMetadata = new File();
fileMetadata.setName("My Report");
fileMetadata.setMimeType("application/vnd.google-apps.spreadsheet");

java.io.File filePath = new java.io.File("files/report.csv");
FileContent mediaContent = new FileContent("text/csv", filePath);
File file = driveService.files().create(fileMetadata, mediaContent)
    .setFields("id")
    .execute();
System.out.println("File ID: " + file.getId());

Upload a file with Groogle

DriveUpload.groovy
DriveScript.instance.uploadFile{
    content 'test.docx' as File
    saveAs GoogleDoc
}

Buscar ficheros

DriveScript.instance.with{
    withFiles {
        nameStartWith 'ABC'
        batchSize 20
        eachFile { file ->
            println file.name
        }
    }
}

Sheet

  • Abrir un SpreadSheet

  • Navegar por las filas de un Sheet

  • Escribir y leer en un Sheet

Leer Sheet

SheetScript.instance.withSpreadSheet 'a2312-sadfx', { spreadSheet ->
    withSheet 'Hoja 1',{ sheet ->
        def str = A2
        println "A2 vale $str"

        def R2 = readCell("A2")
        println "A2 vale con readCell $R2"

        def A2B2 = readRows("A2", "B2")
        A2B2.each{
            println it
        }
    }
}

Escribir Sheet

SheetScript.instance.withSpreadSheet '12321Axsadf-12', { spreadSheet ->
    withSheet 'Hoja 1',{ sheet ->
        A1 = 'Hola'
        B1 = 'Caracola'
    }
}

Calendar

  • Navegar por los eventos de un Calendar

  • Escribir en un Calendar

Nuevo evento

CalendarScript.instance.createEvent groogleCalendarId, {
    it.event.summary = "quedada"
    allDay new Date().format('yyyy-MM-dd')
}

Leer eventos

CalendarScript.instance.withCalendar( groogleCalendarId,{
    batchSize(20)
    eachEvent{ WithEvent withEvent->
        println "Evento $withEvent.event.summary"
    }
})

Modificar evento

CalendarScript.instance.withCalendar( groogleCalendarId,{ WithCalendar withCalendar->
    batchSize(20)
    eachEvent{ WithEvent withEvent->
        withEvent.event.summary = "modificado ${new Date()}"
        moveTo new Date().format('yyyy-MM-dd')
    }
})

Demo Time