Save 37% off PRO during our Black Friday Sale! »

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)

5778521f67d80de0ee3b213e4f159a59?s=128

Sébastien Deleuze

April 17, 2014
Tweet

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
  2. 2 OpenSnap is a HTML5 clone of SnapChat

  3. 3 Two way communication with Websocket 3 Serveur Client Client

    Client Websocket Asynchronous Non blocking
  4. 4 The technology stack should be Fun Easy to use

    Scalable Future proof Efficient
  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
  6. 6 Project layout

  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
  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"));       };     }   }
  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
  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
  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+
  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
  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
  14. 14 Stomp MESSAGE   destination:/usr/authenticated   content-­‐type:application/json;charset=UTF-­‐8   ! {"username":"seb","password":null,"roles":["USER","ADMIN"]}

  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
  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);   }
  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);   }
  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;     }   }
  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<Snap>  create(Snap  snap);     CompletableFuture<Snap>  getById(int  id);   }
  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
  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
  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
  23. 23 Demo: http://opensnap.io

  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
  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
  26. 26 Dart platform Language Tooling Virtual machine Documentation API Web

    frameworks dart2js transpiler Package repository
  27. 27 Dart and browsers : Javascript is the Web's bytecode

    233 Kbytes Javascript file
  28. Dart language 28

  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)}');   }
  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?';   }
  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'));   }
  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
  33. Streams Initial loading of data Subscriptions Messages Broker Application Data

    Page Binding 33
  34. public  class  Snap  {     private  int  id;  

      private  User  author;     private  List<User>  recipients;     private  String  photo;     private  int  duration;   !   public  Snap(int  id,  User  author,     List<User>  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<User>  recipients;      String  photo;      int  duration;            Snap(this.author,  this.recipients,        this.photo,  this.duration,        [this.id  =  null]);   }
  35. 35 Dart: subscription + initial data loading class  SnapService  {

         List<Snap>  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<List<Snap>>  receivedSnaps()  {          return  _client.jsonSubscribeRequest("/app/snap/received",     (_)  =>  Snap.fromJsonList(map));      }   }
  36. 36 AngularDart: binding <ul>      <li  ng-­‐repeat="snap  in  ctrl.snaps">

             Snap  {{snap.id}}  from{{snap.author.username}}      </li>   </ul> @NgComponent(          selector:  'snaps-­‐received',          templateUrl:  'snaps_received.html',          applyAuthorStyles:  true,          publishAs:  'ctrl'   )   class  SnapsReceivedComponent  {      SnapService  _snapService;      List<Snap>  get  snaps  =>  this._snapService.snapsReceived;      SnapsReceivedComponent(this._snapService);   }
  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()));      }   }
  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
  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
  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'          //  ...   }
  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
  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'   }
  43. 43 IDE § IntelliJ IDEA 13 § Java 8 and

    Gradle support § Plugin DART ! ! § Dart Editor § Based on Eclipse runtime § Better error feedback
  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
  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 …