Testdrive AngularJS with Spring 4

Post on 12-Jun-2015

5.680 views 5 download

description

A simple application called cvdb is used to illustrate best practices in combining AngularJS as a client browser technology with a Spring based Java server. The server architecture utilizes the new Spring Boot module that was introduced with Spring 4 together with other Spring modules like Spring Data, Spring Security, Spring MVC. QueryDSL is used to access a H2 in memory database.

Transcript of Testdrive AngularJS with Spring 4

© 2014 INFINIT GmbH

Dr. Oliver Wahlenoliver.wahlen@infinit-group.de

!

September 2014

TestdriveAngularJS with Spring 4

1

© 2014 INFINIT GmbH 2

MotivationINFINIT creates software products for customersSpecialized on Java server and mobile technologyHardware & Software are constantly improving at all levelsMarket success of fixed price projects = Efficiency + Quality

We are constantly improving our processes and tools

It’s Fun!!!

© 2014 INFINIT GmbH 3

Challenges of JavaEE projectsusing EJB and JSF…

fixed component set (xxxFaces) is difficult to customizeslow due to JSF lifecycle / application statehard to integrate with pure Javascriptdoes not scale because sessions are hugeno integrated testing concept: views, unit, integration, functionvariety of IoC frameworks with different constraints (JSF, EJB, CDI)slow development roundtrips(no hot reloading, monolithic App Server)

© 2014 INFINIT GmbH 4

Challenges of JavaScript projectspure jQuery is no good idea:

no structure by convention: how to stay clean?no test support

what events are bound to what components?maintaining high quality with many developers is difficultJavascript is becoming more important in mobile apps

© 2014 INFINIT GmbH 5

Motivation for AngularJSUI experience of Single Page ApplicationsElegant programmers model based on JavaScript:Model View Whatever patternDependency InjectionModularization using modules, directivesTesting supportPopularity and community support

© 2014 INFINIT GmbH 6

What is new in Spring 4New project Spring Boot: Convention over ConfigurationIntegrates with Spring Loaded: reloading classes and resourcesApproved, consistent and complete tool chain:

Spring Core: Dependency Injection,Transaction Mgmt, testing, etc.Spring Data: Consistent approachto access data stores, QuerydslSpring Security … many more

© 2014 INFINIT GmbH 7

The CV Database Application

Source Code: https://bitbucket.org/infinit-group/cvdbDemo App: http://cvdb.elasticbeanstalk.com/

Application is as responsive as a desktop appCV Database is just complex enough for real-world template

© 2014 INFINIT GmbH 8

Software Architecture

© 2014 INFINIT GmbH 9

Client Tooling: JavaScriptnpm Node Packet Manager installs NodeJS packages Yeoman Code Generator generates AngularJS project skeleton from shell Grunt build tool executes tasks defined in JSON file (similar to ant) Bower dependency resolver fetches dependent JS components from Repos (similar to ivy)Karma test driver runs JS tests in JS engine (e.g. phantomjs, Browser with plugin) Jasmine testing Framework tests are written in Jasmine format (similar to Spock) JSHint JavaScript validator ensures good JavaScript code style (IDE integration)

JavaScript uses its own optimized toolchain! -> Bad idea: use java tools like maven, junit, …

© 2014 INFINIT GmbH 10

Server Tooling: Javagradle build tool incl. dependency resolver, test driver gradle grunt plugin maps grunt tasks onto gradle tasks spring-boot gradle plugin adds Spring Boot specific tasks Spring Boot Convention over Configuration Framework Embedded Tomcat servlet container managed by Spring Boot

Gradle is used as master build system: orchestrates grunt and builds Java

© 2014 INFINIT GmbH 11

Overall Project Structurecvdb-client separate project for AngularJS clientcvdb-serverSpring based sever code that includes client WARdockeroptionally packaging for Elastic Beanstalkslidesthese slides

© 2014 INFINIT GmbH 12

Client Side - AngularJS Partviews html fragments with AngularJS tags controllers two way binding view elements, actions, routingservices singletons for business logic, DAO layerinterceptors for request&responses (e.g. Security)directives componentization, encapsulation of DOM manipulationstests techniques to test all components other images, css, fonts, etc.

© 2014 INFINIT GmbH 13

Enough Theory! Where is the Code?

Note: For education the following code examples are simplifications taken from https://bitbucket.org/infinit-group/cvdb

© 2014 INFINIT GmbH 14

The entry page: index.html<!doctype html>! <!-- Link body tag with AngularJS module (see app.js) -->! <body ng-app="cvdbClientApp">!! <!-- include html fragments -->! <div ng-include="'views/header.html'"></div>!! <!-- define main view used by routing (usage of $routeProvider)-->! <div class="container" ng-view=""></div>!! <!-- build:js script tags generated by grunt -->! <!-- external scripts retrieved by bower -->! <script src="bower_components/angular/angular.js"></script>! <!-- script files of the application -->! <script src="scripts/app.js"></script>! <script src="scripts/controllers/headercontroller.js"></script>! <!-- endbuild -->! </body>!</html> Using AngularJS to build a single page application

© 2014 INFINIT GmbH 15

Central AngularJS Configuration: app.js// Definition of dependant modules!angular.module('cvdbClientApp', [! 'ngRoute', // makes $routeProvider injectable! 'ui.bootstrap'!])! .config(['$httpProvider', '$routeProvider', function ($httpProvider, $routeProvider) {! // Configuration phase - only providers injectable!! // Spring security will respond with a "WWW-Authenticate" header! // if the "X-Request-With" header is not sent in the request.! // This will in turn trigger a login popup in the browser.! $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';!! $routeProvider! .otherwise({! // default view to be used if no other URL matcher fits! redirectTo: '/resume/list'! });! }])! .run(function () {! // bootstrap the application - only instances are injectable! /* global FastClick: false */! FastClick.attach(document.body);! });

© 2014 INFINIT GmbH 16

AngularJS Controller: ResumeController.jsangular.module('cvdbClientApp')! .config(['$routeProvider', function ($routeProvider) {! $routeProvider! .when('/resume/list', {! templateUrl: '/views/resume/list.html',! controller: 'ResumeListController'! })! }])! .controller('ResumeListController', ['$scope', '$location', 'ResumeService', function ($scope, $location, ResumeService) {!! // Actions live in the $scope hierarchy: one $scope per Controller! $scope.onRowClicked = function (row) {! // change browser URL, active Controller and view! $location.path('/resume/show/' + row.id);! };!! // ResumeService is used to load rows asynchronously! ResumeService.list().$promise.then(! // 'then' is called back when received response! function (result) {! // 'mode' is bindable from view! $scope.model = result;! });! };! }]);

Routing URLs to Controllers

© 2014 INFINIT GmbH 17

Associated HTML template: list.html<h2>Resumes</h2>!<p>! <!-- binding a button to a create action -->! <button class="btn btn-primary" ng-click="create()">! <i class="fa fa-plus fa-lg"></i> Add Resume! </button>!</p>!!<!-- table is rendered by custom directive -->!<!-- 'content' is a JSON element from the server response -->!<my-table rows="model.content" on-row-clicked="onRowClicked(row)">!</my-table>

© 2014 INFINIT GmbH 18

Custom Directive for tables: mytable.jsangular.module('cvdbClientApp')! .directive('myTable', function () {! return {! templateUrl: '/views/directives/mytable.html',! restrict: 'E', // directive applies to a html element! scope: {! rows: '=', // bind row attribute to directive's $scope! onRowClicked: '&' // bind attribute to function reference! },! controller: ['$scope', function ($scope) {! // called on click in row passing the row as parameter! $scope._onRowClicked = function (row) {! $scope.onRowClicked({row: row});! };! }]! };! });

© 2014 INFINIT GmbH 19

Directive’s HTML template: mytable.html<table>! <!-- iterate over rows and colums of directive's scope -->! <tr ng-repeat="row in rows" ng-click="_onRowClicked(row)">! <td ng-repeat="column in [0,1]">! {{row[column]}}! </td>! </tr>!</table>

© 2014 INFINIT GmbH 20

Consuming Server Data: resumeservice.jsangular.module('cvdbClientApp')! .service('ResumeService', ['$resource', function ($resource) {! this._apiUrl = '/api/resume'; // define the REST URL! this._apiUrlId = this._apiUrl + '/:id';!! this.list = function () {! // $resource encapsulates asynchronous REST calls! var resource = $resource(this._apiUrl);! return resource.get({page: 0, size: 1000});! };!! this.get = function (id) {! return $resource(this._apiUrlId).get({id: id});! };!! this.remove = function (id) {! return $resource(this._apiUrlId).delete({id: id});! };!! this.save = function (resume) {! return $resource(this._apiUrl).save(resume);! };! }]);

© 2014 INFINIT GmbH 21

Authentication: responseinterceptor.jsangular.module('cvdbClientApp')! .factory('responseInterceptor', ['$q', '$location', function($q, $location) {! return {! responseError: function (rejection) {! if (rejection.status === 401) {! $location.path('/auth/login');! } else {! console.log('Error processing request: '! + rejection.statusText! + ' [' + rejection.status + ']');! }! return $q.reject(rejection);! }! };! }])! .config(['$httpProvider', function ($httpProvider) {! $httpProvider.interceptors.push('responseInterceptor');! }]);

© 2014 INFINIT GmbH 22

Server Side - Spring Partconfig Config Beans with Spring Configurers controller Spring MVC controllers domain folder for JPA-Entities repositories Spring Data repositories services Spring Beans with business logic utils.securityUtility classes for Spring Security

© 2014 INFINIT GmbH 23

Spring Configuration without XML@Configuration // annotation for configuration Bean!public class MyBeanDefinitions {! @Bean // annotation for bean definition! public PasswordEncoder passwordEncoder() {! // Method name = Bean name! // Method type = Bean type! // Spring calls the method to create the bean! return new StandardPasswordEncoder();! }!};!!

@Component // annotation for a Spring Bean!public class MyBean {! @Autowired // Inject a Spring Bean! private PasswordEncoder passwordEncoder;!}

© 2014 INFINIT GmbH 24

Spring Boot Configurations

@EnableAutoConfiguration // Enable Magic!@Controller // Define MVC Controller!public class SampleController {! @RequestMapping("/")! @ResponseBody! String home() {! return "Hello World!";! }!!

public static void main(String[] args) throws Exception {! SpringApplication.run(SampleController.class, args);! }!}

An extreme simple but working Spring MVC application!

© 2014 INFINIT GmbH 25

@EnableAutoConfiguration

Our Experience: Using Convention over Configuration you can create applications efficiently.But you have to know the Conventions!

/**!Enable auto-configuration of the Spring Application Context,!attempting to guess and configure beans that you are likely to need.!Auto-configuration classes are usually applied based on your!classpath and what beans you have defined.!**/

© 2014 INFINIT GmbH 26

Spring’s Convention Implementation [1]@Configuration!// ...!public class WebMvcAutoConfiguration {! // ...! @Bean! @ConditionalOnMissingBean(RequestContextListener.class)! public RequestContextListener requestContextListener() {! return new RequestContextListener();! }! // ...!}

Conditional configurations: Sprint Boot Beans are only usedif Bean with that class is not yet contained in BeanFactory.

© 2014 INFINIT GmbH 27

Spring’s Convention Implementation [2]@Configuration!public class Security extends WebSecurityConfigurerAdapter {! // ...! @Override // Override standard configurations! protected void configure(final HttpSecurity http)! throws Exception {! http! .httpBasic() // basic authentication shall be used! http! .rememberMe(); // rememberMe cookies shall be honoured! http! .csrf().disable() // disable XSS checks with static pages! .authorizeRequests() // define request authorization! // admins can monitor! .antMatchers("/monitoring/**").hasRole("ADMIN")! // API calls must be authenticated! .antMatchers("/api/**").authenticated()! // static content is allowed for anyone - use a CDN!! .anyRequest().permitAll()! }! // ...!}

Security Configurers based on @Override

© 2014 INFINIT GmbH 28

Spring Data

"Makes it easy to use new data access technologies, such as non-relational databases"

Spring Data JPA: part of the larger Spring Data family […] makes it easy to implement JPA based repositories.

© 2014 INFINIT GmbH 29

JPA Repositoriespublic interface UserRepository! extends PagingAndSortingRepository<User, Long> {!! User findByUsername(String username);!}!!// Spring Code below this line:!public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {!! Iterable<T> findAll(Sort sort);!! Page<T> findAll(Pageable pageable);!}!!public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {! <S extends T> S save(S entity); // save the entity! T findOne(ID id); // lookup the entity! void delete(T entity); // delete the entity! // and many other methods...!}

Define a custom interface and Spring creates the DAO implementation

© 2014 INFINIT GmbH 30

Implementing Custom DAO Methods// create a custom interface for the custom DAO method!public interface ResumeRepositoryCustom {! // custom DAO method! Page<Resume> search(String search, Pageable pageable);!}!!// create an implementation class ending with "Impl"!public class ResumeRepositoryImpl! implements ResumeRepositoryCustom {! @Override! public Page<Resume> search(String search, Pageable pageable) {! // perform custom query and return result page! // removed for simplicity! }!}!!// extend Spring Repository and custom implementation!public interface ResumeRepository! extends PagingAndSortingRepository<Resume, Long>,! ResumeRepositoryCustom {!}

© 2014 INFINIT GmbH 31

QueryDSL for SQL QueriesMotivation: JPA has some significant disadvantages:

JPQL String concatenation leads to mess:no Compiler or IDE-SupportCriteriaBuilder is unreadable for queries of relevant sizeNative queries are way more powerful than JPA queries

QueryDSL is a DB-Query Technology that is:Type Safe (in contrast to String concatenation)Readable (in contrast to CriteriaBuilder API)Supports for JPA queries and native queries

© 2014 INFINIT GmbH 32

QueryDSL Example// Query using JPA EntityManager!JPQLQuery query = new JPAQuery (entityManager);!cats = query.from(cat)! .innerJoin(cat.mate, mate)! .orderBy(cat.name.asc())! .list(cat);!!// Query and project into DTO!query = new JPASQLQuery(entityManager, templates);!List<CatDTO> catDTOs = query.from(cat)! .orderBy(cat.name.asc())! .list(EConstructor.create(CatDTO.class, cat.id, cat.name));!!// Query in SQL, but project as entity!query = new JPASQLQuery(entityManager, templates);!cats = query.from(cat)! .innerJoin(mate).on(cat.mateId.eq(mate.id))! .where(cat.dtype.eq("Cat"), mate.dtype.eq("Cat"))! .list(catEntity);

QueryDSL allows: type save queriescomplex subselectsreturning entities or DTOs

© 2014 INFINIT GmbH 33

QueryDSL DrawbackSourcecode Generator is Needed:

generated Java Source Code contains Entity metadataQueryDSL uses the Java-Compiler with an JPAAnnotationProcessor to perform thiscvdb contains a querdsl.gradle file to illustrate the usage.

© 2014 INFINIT GmbH 34

QueryDSL Integration with Spring// Implement Custom Query!public interface ResumeRepositoryCustom {! Page<Resume> search(String search, Pageable pageable);!}!!// Use QueryDsl!public class ResumeRepositoryImpl implements ResumeRepositoryCustom {! @Autowired! private ResumeRepository resumeRepository;!! @Override! public Page<Resume> search(String search, Pageable pageable) {! QResume resume = QResume.resume;! Predicate predicate =! resume.firstName.containsIgnoreCase(search).! or(resume.lastName.containsIgnoreCase(search));! return resumeRepository.findAll(predicate, pageable);! }!}!!// Spring generates an Implementation for the ResumeRepository!public interface ResumeRepository extends! PagingAndSortingRepository<Resume, Long>,! QueryDslPredicateExecutor<Resume>,! ResumeRepositoryCustom {!}

© 2014 INFINIT GmbH

Hot Code Replacement also known as Hot SwappingAllows exchanging of classes and/or resources in a running application or debugging session.!

!

!

!

!

Spring-Loaded can reload class definitions with changed method signaturesSimilar to JRebel but less powerfullIntegrated in gradle bootRun

35

Hot Code Replacement

© 2014 INFINIT GmbH 36

Testing with Spring IoC is available in testsProfiles allow custom test injection setupDatabase can be used for integration testsMockServlet available

© 2014 INFINIT GmbH 37

Profiles and Properties@ContextConfiguration(classes = TestApplication,! loader = SpringApplicationContextLoader)!@ActiveProfiles("test") // activate test profile!@Transactional // rollback after each test method!abstract class TestSpec extends Specification {!}

spring.datasource.driverClassName=org.h2.Driver!spring.datasource.password=!spring.datasource.url=jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000!spring.datasource.username=sa!spring.jpa.database-platform=org.hibernate.dialect.H2Dialect!spring.jpa.hibernate.ddl-auto=create!spring.jpa.show-sql=false

Test Baseclass

application-test.properties

© 2014 INFINIT GmbH 38

Testing Controllersdef "get resume should get 20 results"() {! when:! ResultActions result = mockMvc.perform(get('/api/resume')! .contentType(MediaType.APPLICATION_JSON)! )!! then:! result.andExpect(status().isOk())! result.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))! result.andExpect(jsonPath('$.content', hasSize(20)))!}

© 2014 INFINIT GmbH 39

ConclusionAngularJS

removes server stateenables native application experienceputs view logic into Javascriptrequires new skill profiles and processes!

Spring 4 introduces Convention over Configuration with Spring Bootcomes with ecosystem of sub projectsallows for efficient development

© 2014 INFINIT GmbH 40

Thank Your For Your Attention

Visit Us Van-der-Smissen-Straße 322767 Hamburg

!

Tel: +49 (0)40 38 65 18 80Fax: +49 (0)40 38 65 18 81

!

Web: www.infinit-group.deE-Mail:info@infinit-group.de