$30 off During Our Annual Pro Sale. View Details »

Reactive applications with Spring
, AngularDart and Websocket

Reactive applications with Spring
, AngularDart and Websocket

This talk is a feedback about developing reactive applications with Spring on server-side and Dart on client-side, based on OpenSnap, a HTML5 clone of SnapChat.

Code: https://github.com/sdeleuze/opensnap
Demo: http://opensnap.io (Chrome only)

Sébastien Deleuze

April 17, 2014
Tweet

More Decks by Sébastien Deleuze

Other Decks in Technology

Transcript

  1. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Reactive applications with Spring

    AngularDart and Websocket
    Sébastien Deleuze, Spring Framework Commiter and Dart GDE, Pivotal
    @sdeleuze

    View Slide

  2. 2
    OpenSnap is a HTML5 clone of SnapChat

    View Slide

  3. 3
    Two way communication with Websocket
    3
    Serveur
    Client
    Client
    Client
    Websocket Asynchronous
    Non blocking

    View Slide

  4. 4
    The technology stack should be
    Fun
    Easy to use
    Scalable
    Future proof
    Efficient

    View Slide

  5. 5
    The stack
    5
    Spring
    Framework 4
    Spring

    Boot
    Tomcat 8
    Java 8 RabbitMQ
    SockJS
    Gradle
    Bootstrap
    AngularDart
    Dart
    HTML 5
    Server Client
    Build
    Idea
    MongoDB

    View Slide

  6. 6
    Project layout

    View Slide

  7. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Server

    View Slide

  8. 8
    Spring Boot + Java 8
    @Configuration  
    @EnableAutoConfiguration  
    @ComponentScan  
    public  class  Application  extends  SpringBootServletInitializer  {  
       
      //  Run  the  app  with  a  simple  java  -­‐jar  opensnap.jar  
      public  static  void  main(String[]  args)  {  
        SpringApplication.run(new  Object[]{Application.class},  args);  
      }  
       
      @Bean  
      public  InitializingBean  populateTestData(UserService  us)  {  
        return  ()  -­‐>  {  
          us.create(new  User("seb",  "s3b"));  
          us.create(new  User("adeline",  "ad3l1n3"));  
          us.create(new  User("johanna",  "j0hanna"));  
          us.create(new  User("michel",  "m1ch3l"));  
        };  
      }  
    }

    View Slide

  9. 9
    Spring Boot: this is (really) a revolution
    § Starters (data-jpa, web, websocket, security)
    § Advanced configuration made easier
    § JSON endpoints (/beans, /env,

    /mappings, /health, /metrics …)
    § Conditional bean activation
    § Awesome reference documentation
    § Yaml configuration for your app
    § Spring Loaded integration

    View Slide

  10. 10
    What’s new in Spring Framework 4
    § Java 8 support
    § WebSocket, SockJS and STOMP
    § AsyncRestTemplate
    § Generic types used as qualifier
    § « Bill Of Materials » Maven
    § @RestController
    § Groovy Bean Definition DSL

    View Slide

  11. 11
    Focus on Websocket support
    § Support of several Websocket engines
    § JSR 356
    § native Jetty engine
    § Supported containers
    • Jetty 9.1+
    • Tomcat 7.0.47+ et 8.0+
    • Widlfly 8.0+
    • Glassfish 4.0+

    View Slide

  12. 12
    SockJS fallback options
    § Websocket is not always possible (proxy, IE)
    § Spring Framework 4 supports SockJS protocol
    § Client side: thin layer on Websocket API with
    several possible transports (Websocket, http
    polling, iframe …)
    § Server side: a simple option to activate
    § Must have for every large audience website

    View Slide

  13. Stomp over
    Websocket/SockJS
    13
    Programming model
    § Websocket API too low level for application
    development
    § Stomp over Websocket/SockJS
    § SocketIO simplicity + the power of a real
    broker …
    § Client side: stomp.js or Dart Stomp client
    § Server side: methods annotated with
    @MessageMapping and
    @SubscribeMapping in you controller
    Server
    Client

    View Slide

  14. 14
    Stomp
    MESSAGE  
    destination:/usr/authenticated  
    content-­‐type:application/json;charset=UTF-­‐8  
    !
    {"username":"seb","password":null,"roles":["USER","ADMIN"]}

    View Slide

  15. 15
    Architecture
    Client
    Other applications
    Controller methods annotated with

    @MessageMapping and @SubscribeMapping
    /app/*
    /topic/* /queue/*
    Response to:

    /user/* a specific user

    /topic/* broadcast to subscribers

    /queue/* to the first one who retrieves the message
    Client
    Broker

    (embedded or external)
    Response
    Stomp message

    View Slide

  16. 16
    Spring controller: request - response
    1 2
    16
    @Controller  
    public  class  SnapController  {  
       
      @Autowired  private  SnapService  service;  
       
      @SubscribeMapping("/snap/id/{id}")  
      Snap  getById(@DestinationVariable  int  id)  {  
        return  service.getById(id);  
      }    
    }
    public  interface  SnapService  {  
      Snap  create(Snap  snap);  
      Snap  getById(int  id);  
    }

    View Slide

  17. 17
    Spring controller: request - broadcast
    1
    2 2 2
    @Controller  
    public  class  SnapController  {  
         
      @MessageMapping("/snap/create")  
      @SendTo("/topic/snap-­‐created")  
      Snap  create(Snap  snap)  {  
        return  service.create(snap);  
      }  
    }
    public  interface  SnapService  {  
      Snap  create(Snap  snap);  
      Snap  getById(int  id);  
    }

    View Slide

  18. 18
    Send a message from anywhere
    @Service  
    public  class  SnapService  {  
       
      @Autowired  private  SimpMessagingTemplate  template;  
       
      public  Snap  create(Snap  snap)  {  
        snap.setId(snapCounter.getAndIncrement());  
        snaps.add(snap);  
        for(User  user  :  snap.getRecipients())  {  
          template.convertAndSendToUser(user.getUsername(),  
            "/queue/snap-­‐received",  snap.withoutPhoto());  
        }  
        return  snap;  
      }  
    }

    View Slide

  19. 19
    Serveur : asynchronous send
    @Controller  
    public  class  SnapController  {  
       
      @Autowired  
      private  SimpMessagingTemplate  template;  
       
      @MessageMapping("/snap/create")  
      void  create(Snap  snap,  Principal  principal)  {  
        snapService.create(snap).thenAccept(createdSnap  -­‐>  
          template.convertAndSendToUser(principal.getName(),  
            "/queue/snap-­‐created",  createdSnap)  
        );  
      }  
    }
    public  interface  SnapService  {  
      CompletableFuture  create(Snap  snap);  
      CompletableFuture  getById(int  id);  
    }

    View Slide

  20. 20
    Broker
    § Thanks to Stomp, you have the choice
    • SimpleBroker: pure Java, included out of the box
    • External broker: RabbitMQ, Appolo …
    !
    § Why should I use an external broker?
    • Broadcast in cluster mode
    • Monitoring
    • Performance tuning
    • Interoperability
    • Message persistence

    View Slide

  21. 21
    Security
    § HTTP based authentification
    • Easy to use with Spring Security
    • Session cookie transmitted during
    Websocket handshake
    • No Stomp credential support yet
    § Authorizations
    • Possible by implementing a custom
    ChannelInterceptor (cf. OpenSnap)
    • Inluded in future Spring Framework and
    Spring Security releases
    app:  
       '*':  'USER'  
    user:  
       queue:  
           '*':  'USER'  
           'error':  'ANONYMOUS'  
    queue:  
       'snap-­‐created':  'ADMIN'  
    topic:  
       'user-­‐authenticated':  'USER'  
       'snap-­‐created':  'USER,ADMIN'
    stomp-­‐security.yml

    View Slide

  22. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Demo

    View Slide

  23. 23
    Demo: http://opensnap.io

    View Slide

  24. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Client

    View Slide

  25. 25
    Dart
    § Structured and flexible language created
    to improve maintainability and efficiency
    of web developments
    § Open Source
    § Pending ECMA normalisation process
    § Current version : Dart 1.3

    View Slide

  26. 26
    Dart platform
    Language
    Tooling
    Virtual machine
    Documentation
    API
    Web frameworks
    dart2js
    transpiler Package repository

    View Slide

  27. 27
    Dart and browsers : Javascript is the Web's bytecode
    233 Kbytes Javascript file

    View Slide

  28. Dart language
    28

    View Slide

  29. 29
    Dart: classes and optional typing
    import  'dart:math';

    !
    class  Point  {  
       final  num  _x,  _y;  
       num  get  x  =>  _x;  
       num  get  y  =>  _y;  
         
       Point(this._x,  this._y);  
       
       num  distanceTo(Point  other)  {  
           var  dx  =  _x  -­‐  other.x;  
           var  dy  =  _y  -­‐  other.y;  
           return  sqrt(dx  *  dx  +  dy  *  dy);  
       }  
    }  
       
    main()  {    
       var  a  =  new  Point(2,  3);  
       var  b  =  new  Point(3,  4);    
       print('distance  from  a  to  b  =  ${a.distanceTo(b)}');  
    }

    View Slide

  30. 30
    Dart: implicit interfaces
    class  Person  {  
       final  _name;  
       Person(this._name);  
       String  greet(who)  =>  'Hello,  $who.  I  am  $_name.';  
    }  
       
    class  Bro  implements  Person  {  
       String  greet(who)  =>  'Hi  $who.  What’s  up?';  
    }

    View Slide

  31. 31
    Dart: future API
    Future  authenticate()  {  
           return  userService.signout()  
                           .then((_)  =>  userService.signin(user))  
                           .then((_)  =>  router.go('photo',  new  Map()))  
                           .catchError((_)  =>  window.alert('Error  during  login'));  
    }

    View Slide

  32. 32
    AngularDart
    § Could be seen just as an AngularJS port
    § Maybe the best Angular version currently
    available
    § Quite young, but progress very quickly
    § Awesome to use
    § Reusable Bootstrap components with
    AngularDart UI

    View Slide

  33. Streams
    Initial loading of data
    Subscriptions
    Messages
    Broker
    Application
    Data
    Page
    Binding
    33

    View Slide

  34. public  class  Snap  {  
      private  int  id;  
      private  User  author;  
      private  List  recipients;  
      private  String  photo;  
      private  int  duration;  
    !
      public  Snap(int  id,  User  author,    
    List  recipients,  String  photo,  int  duration)  {  
        this.id  =  id;  
        this.author  =  author;  
        this.recipients  =  recipients;  
        this.photo  =  photo;  
        this.duration  =  duration;  
      }  
    !
      public  User  getAuthor()  {  
        return  author;  
      }  
    !
      public  void  setAuthor(User  author)  {  
        this.author  =  author;  
      }  
             //  ...  
    }
    34
    Domain model
    class  Snap  {  
       int  id;  
       User  author;  
       List  recipients;  
       String  photo;  
       int  duration;  
         
       Snap(this.author,  this.recipients,  
         this.photo,  this.duration,  
         [this.id  =  null]);  
    }

    View Slide

  35. 35
    Dart: subscription + initial data loading
    class  SnapService  {  
       List  snapsReceived  =  new  List();  
       StompClientService  _client;  
       UserService  _userService;  
       
       SnapService(this._client,  this._userService)  {  
           _userService.onLogin.listen((_)  {  
               _client.jsonSubscribe(‘/user/queue/snap-­‐received",  
        (snap)  =>  snapsReceived.add(new  Snap.fromJsonMap(snap)));  
               receivedSnaps().then(  
        (snaps)  =>  snapsReceived  =  snaps);  
           });  
       }  
    !
       Future>  receivedSnaps()  {  
           return  _client.jsonSubscribeRequest("/app/snap/received",  
      (_)  =>  Snap.fromJsonList(map));  
       }  
    }

    View Slide

  36. 36
    AngularDart: binding
     
         
           Snap  {{snap.id}}  from{{snap.author.username}}  
         

    @NgComponent(  
           selector:  'snaps-­‐received',  
           templateUrl:  'snaps_received.html',  
           applyAuthorStyles:  true,  
           publishAs:  'ctrl'  
    )  
    class  SnapsReceivedComponent  {  
       SnapService  _snapService;  
       List  get  snaps  =>  this._snapService.snapsReceived;  
       SnapsReceivedComponent(this._snapService);  
    }

    View Slide

  37. 37
    HTML5 MediaStream API
    @NgComponent(...)  
    class  PhotoComponent  extends  NgShadowRootAware  {  
       //  ...    
       void  onShadowRoot(ShadowRoot  shadowRoot)  {  
           video  =  shadowRoot.querySelector('#video');  
           canvas  =  shadowRoot.querySelector('#canvas');  
           photo  =  shadowRoot.querySelector('#photo');  
           window.navigator.getUserMedia(audio:  false,  video:  true)  
               .then((stream)  =>  
        video.src  =  Url.createObjectUrlFromStream(stream));  
       }  
       void  takePicture()  {  
           canvas.context2D.drawImage(video,  0,  0);  
           _data  =  canvas.toDataUrl('image/png');  
           photo.src  =  _data;  
       }  
       void  sendSnap()  {  
           Snap  snap  =  new  Snap(_data);  
           _snapService.createSnap(snap)  
               .then((Snap  snap)  =>  _router.go('sent',  new  Map()));  
       }  
    }

    View Slide

  38. 38
    Pushstate
    void  routeInit(Router  r,  RouteViewFactory  f)    {  
       r.root  
           ..addRoute(  
               name:  'signin',  path:  '/signin',  
               enter:  view('view/signin.html'),

               defaultRoute:  true)  
           ..addRoute(  
                   name:  'photo',  path:  '/photo',  
                   enter:  view('view/photo.html'))  
           ..addRoute(  
                   name:  'received',  path:  '/received',  
                   enter:  view('view/snaps_received.html'))  
           ..addRoute(  
                   name:  'sent',  path:  '/sent',  
                   enter:  view('view/snaps_sent.html'))  
           ..addRoute(  
                   name:  'admin',  path:  '/admin',  
                   enter:  view('view/admin.html'));    
    }
    @Configuration  
    public  class  PushStateConfig  extends  WebMvcConfigurerAdapter  {  
       public  void  addViewControllers(ViewControllerRegistry  r)  {  
           r.addViewController("/")  
             .setViewName("forward:/index.html");  
           r.addViewController("/signin")  
             .setViewName("forward:/index.html");  
           r.addViewController("/photo")  
             .setViewName("forward:/index.html");  
           r.addViewController("/sent")  
             .setViewName("forward:/index.html");  
           r.addViewController("/received")  
             .setViewName("forward:/index.html");  
           r.addViewController("/admin")  
             .setViewName("forward:/index.html");  
           r.addViewController("/logout")  
             .setViewName("forward:/index.html");  
       }  
    }
    38

    View Slide

  39. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Build & Dev

    View Slide

  40. 40
    Server side build.gradle
    buildscript  {  
           dependencies  {  
                   classpath  'org.springframework.boot:spring-­‐boot-­‐gradle-­‐plugin:1.0.1.RELEASE'  
           }  
    }  
       
    apply  plugin:  'java'  
    apply  plugin:  'spring-­‐boot'  
       
    sourceCompatibility  =  '1.8'  
    targetCompatibility  =  '1.8'  
       
    dependencies  {  
           compile  project(':opensnap-­‐client')  
           compile  'org.springframework.boot:spring-­‐boot-­‐starter-­‐actuator'  
           compile  'org.springframework.boot:spring-­‐boot-­‐starter-­‐websocket'  
           compile  'org.projectreactor:reactor-­‐tcp:1.0.1.RELEASE'  
           compile  'org.springframework.security:spring-­‐security-­‐web:3.2.3.RELEASE'  
           //  ...  
    }

    View Slide

  41. 41
    pubspec.yaml + client side build.gradle
    name:  opensnap  
    version:  0.2.0  
    dependencies:  
       angular:  0.9.10  
       browser:  any  
       js:  any  
       unittest:  any  
       angular_ui:  '>=0.2.2  <0.3.0'  
       stomp:  0.7.2  
       collection:  any  
       logging:  any  
       route_hierarchical:  '>=0.4.17  <0.5.0'
    apply  plugin:  'java'  
    jar  {  
           from  'build/web'  
           eachFile  {  details  -­‐>  
                   details.path  =  'static/'+details.path  
           }  
    }  
    task  pubClean(type:  Delete)  {  
           delete  'build'  
    }  
    task  pubBuild(type:  Exec)  {  
           executable  'pub'  
           args  'build'  
    }  
    jar.dependsOn  pubBuild  
    clean.dependsOn  pubClean

    View Slide

  42. 42
    Root build.gradle
    defaultTasks  'clean',  'build'  
    task  clean(dependsOn:  [':opensnap-­‐client:clean',  ':opensnap-­‐server:clean',  'cleanBuildDir'])  <<  {}  
    task  build(dependsOn:  [':opensnap-­‐client:build',  ':opensnap-­‐server:build',  'copyJar'])  <<  {}  
    task  run()  <<  {  
      javaexec  {  
        main  =  '-­‐jar'  
        args  '-­‐Dspring.profiles.active=local'  
        args  'build/opensnap.jar'  
      }  
    }    
    task  copyJar(type:  Copy,  dependsOn:  ':opensnap-­‐server:build')  {  
           from('opensnap-­‐server/build/libs')  {  
              include  '**/*.jar'  
              rename  'opensnap-­‐(.*).jar',  'opensnap.jar'  
           }  
           into  'build'  
    }  
    task  cleanBuildDir(type:  Delete)  {  
           delete  'build'  
    }

    View Slide

  43. 43
    IDE
    § IntelliJ IDEA 13
    § Java 8 and Gradle support
    § Plugin DART
    !
    !
    § Dart Editor
    § Based on Eclipse runtime
    § Better error feedback

    View Slide

  44. 44
    Coming soon
    § OpenSnap
    • Dart SockJS support
    • Cluster mode
    § Dart
    • Chrome native support
    • Firefox, IE and Safari support
    § Spring
    • Websocket scope
    • @SentToUser support in cluster mode
    • Resource Handling

    View Slide

  45. Unless otherwise indicated, these slides are © 2013-2014 Pivotal Software, Inc. and licensed under a

    Creative Commons Attribution-NonCommercial license: http://creativecommons.org/licenses/by-nc/3.0/
    Thanks!
    Demo : http://opensnap.io
    Code : https://github.com/sdeleuze/opensnap
    !
    Spring : @springcentral
    Dart : http://g.co/dartisans
    !
    Follow me on @sdeleuze …

    View Slide