Upload
oliver-wahlen
View
5.680
Download
5
Embed Size (px)
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.
Citation preview
© 2014 INFINIT GmbH
Dr. Oliver [email protected]
!
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:[email protected]