More Related Content Similar to Testdrive AngularJS with Spring 4 (20) Testdrive AngularJS with Spring 41. Testdrive
AngularJS with Spring 4
Dr. Oliver Wahlen
oliver.wahlen@infinit-group.de
!
September 2014
© 2014 INFINIT GmbH
1
2. Motivation
INFINIT creates software products for customers
Specialized on Java server and mobile technology
Hardware & Software are constantly improving at all levels
Market success of fixed price projects = Efficiency + Quality
We are constantly improving our processes and tools
It’s Fun!!!
© 2014 INFINIT GmbH 2
3. Challenges of JavaEE projects
using EJB and JSF…
fixed component set (xxxFaces) is difficult to customize
slow due to JSF lifecycle / application state
hard to integrate with pure Javascript
does not scale because sessions are huge
no integrated testing concept: views, unit, integration, function
variety of IoC frameworks with different constraints (JSF, EJB, CDI)
slow development roundtrips
(no hot reloading, monolithic App Server)
© 2014 INFINIT GmbH 3
4. Challenges of JavaScript projects
pure 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 difficult
Javascript is becoming more important in mobile apps
© 2014 INFINIT GmbH 4
5. Motivation for AngularJS
UI experience of Single Page Applications
Elegant programmers model based on JavaScript:
Model View Whatever pattern
Dependency Injection
Modularization using modules, directives
Testing support
Popularity and community support
© 2014 INFINIT GmbH 5
6. What is new in Spring 4
New project Spring Boot: Convention over Configuration
Integrates with Spring Loaded: reloading classes and resources
Approved, consistent and complete tool chain:
Spring Core: Dependency Injection,
Transaction Mgmt, testing, etc.
Spring Data: Consistent approach
to access data stores, Querydsl
Spring Security
… many more
© 2014 INFINIT GmbH 6
7. The CV Database Application
Source Code: https://bitbucket.org/infinit-group/cvdb
Demo App: http://cvdb.elasticbeanstalk.com/
Application is as responsive as a desktop app
CV Database is just complex enough for real-world template
© 2014 INFINIT GmbH 7
9. Client Tooling: JavaScript
npm 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 9
10. Server Tooling: Java
gradle 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 10
11. Overall Project Structure
cvdb-client
separate project for AngularJS client
cvdb-server
Spring based sever code that includes
client WAR
docker
optionally packaging for Elastic
Beanstalk
slides
these slides
© 2014 INFINIT GmbH 11
12. Client Side - AngularJS Part
views html fragments with AngularJS tags
controllers two way binding view elements,
actions, routing
services singletons for business logic,
DAO layer
interceptors for request&responses
(e.g. Security)
directives componentization,
encapsulation of DOM manipulations
tests techniques to test all components
other images, css, fonts, etc.
© 2014 INFINIT GmbH 12
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 13
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>!
© 2014 INFINIT GmbH 14
!
<!-- 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
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!
© 2014 INFINIT GmbH 15
!
// 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);!
});
16. AngularJS Controller: ResumeController.js
angular.module('cvdbClientApp')!
.config(['$routeProvider', function ($routeProvider) {!
© 2014 INFINIT GmbH 16
$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
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 17
18. Custom Directive for tables: mytable.js
angular.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 18
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 19
20. Consuming Server Data: resumeservice.js
angular.module('cvdbClientApp')!
.service('ResumeService', ['$resource', function ($resource) {!
this._apiUrl = '/api/resume'; // define the REST URL!
this._apiUrlId = this._apiUrl + '/:id';!
© 2014 INFINIT GmbH 20
!
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);!
};!
}]);
21. Authentication: responseinterceptor.js
angular.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 21
22. Server Side - Spring Part
config 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.security Utility classes for Spring Security
© 2014 INFINIT GmbH 22
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 23
24. Spring Boot Configurations
@EnableAutoConfiguration // Enable Magic!
@Controller // Define MVC Controller!
public class SampleController {!
@RequestMapping("/")!
@ResponseBody!
String home() {!
return "Hello World!";!
}!
© 2014 INFINIT GmbH 24
!
public static void main(String[] args) throws Exception {!
SpringApplication.run(SampleController.class, args);!
}!
}
An extreme simple but working Spring MVC application!
25. @EnableAutoConfiguration
/**!
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.!
**/
Our Experience: Using Convention over Configuration you can create applications efficiently.
But you have to know the Conventions!
© 2014 INFINIT GmbH 25
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 used
if Bean with that class is not yet contained in BeanFactory.
© 2014 INFINIT GmbH 26
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 27
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 28
29. JPA Repositories
© 2014 INFINIT GmbH 29
public 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
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 30
31. QueryDSL for SQL Queries
Motivation: JPA has some significant disadvantages:
JPQL String concatenation leads to mess:
no Compiler or IDE-Support
CriteriaBuilder is unreadable for queries of relevant size
Native 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 31
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 queries
complex subselects
returning entities or DTOs
© 2014 INFINIT GmbH 32
33. QueryDSL Drawback
Sourcecode Generator is Needed:
generated Java Source Code contains Entity metadata
QueryDSL uses the Java-Compiler with an
JPAAnnotationProcessor to perform this
cvdb contains a querdsl.gradle file to illustrate the usage.
© 2014 INFINIT GmbH 33
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!
© 2014 INFINIT GmbH 34
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 {!
}
35. Hot Code Replacement also known as Hot Swapping
Allows exchanging of classes and/or resources in a running application or
debugging session.
© 2014 INFINIT GmbH
!
!
!
!
!
Spring-Loaded can reload class definitions with changed method signatures
Similar to JRebel but less powerfull
Integrated in gradle bootRun
35
Hot Code Replacement
36. Testing with Spring
IoC is available in tests
Profiles allow custom test injection setup
Database can be used for integration tests
MockServlet available
© 2014 INFINIT GmbH 36
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 {!
}
Test Baseclass
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
application-test.properties
© 2014 INFINIT GmbH 37
38. Testing Controllers
def "get resume should get 20 results"() {!
when:!
ResultActions result = mockMvc.perform(get('/api/resume')!
.contentType(MediaType.APPLICATION_JSON)!
)!
© 2014 INFINIT GmbH 38
!
then:!
result.andExpect(status().isOk())!
result.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))!
result.andExpect(jsonPath('$.content', hasSize(20)))!
}
39. Conclusion
AngularJS
removes server state
enables native application experience
puts view logic into Javascript
requires new skill profiles and processes
!
Spring 4
introduces Convention over Configuration with Spring Boot
comes with ecosystem of sub projects
allows for efficient development
© 2014 INFINIT GmbH 39
40. Thank Your For Your Attention
Visit Us
Van-der-Smissen-Straße 3
22767 Hamburg
!
Tel: +49 (0)40 38 65 18 80
Fax: +49 (0)40 38 65 18 81
!
Web: www.infinit-group.de
E-Mail: info@infinit-group.de
© 2014 INFINIT GmbH 40