Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

2 OpenSnap is a HTML5 clone of SnapChat

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

6 Project layout

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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"));       };     }   }

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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+

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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);   }

Slide 17

Slide 17 text

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);   }

Slide 18

Slide 18 text

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;     }   }

Slide 19

Slide 19 text

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);   }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

23 Demo: http://opensnap.io

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Dart language 28

Slide 29

Slide 29 text

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)}');   }

Slide 30

Slide 30 text

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?';   }

Slide 31

Slide 31 text

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'));   }

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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]);   }

Slide 35

Slide 35 text

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));      }   }

Slide 36

Slide 36 text

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);   }

Slide 37

Slide 37 text

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()));      }   }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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'          //  ...   }

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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'   }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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 …