-
Spring Framework + Spring Boot - well, what else? :-)
-
Spring Data JPA - for rapid development of the data access layer (database mapping, CRUD repository support, JPQL query for search and listing of articles - all done declaratively!). (Note: I chose to map authors and keywords as
List<String>
instead of doing full-blown data modeling to focus on other aspects of the task but keep in mind that even with this simple@ElementCollection
mapping those are kept in a separate table so refactoring / expanding the model and JPA mapping shouldn't be a big problem in the future.) -
Spring Security - HTTP Basic security for editor-only parts of the API and for the management API. Method security for the service layer (see
ArticleService
). -
REST Assured - for REST API testing (see
RestApiTests
). -
HyperSQL (HSQLDB) - as an embedded RDBMS.
To build the code you need JDK 8 and Maven >= 3.2.
- Clone repo.
mvn clean package
This builds a Spring Boot executable jar file with embedded Tomcat.
cd target
java -jar articles-0.0.1-SNAPSHOT.jar
This should start the embedded Tomcat and the application will be available at http://localhost:8080/.
curl -v -X PUT --user editor:s3cr3t -H "Content-Type: application/json" -d @example.json http://localhost:8080/api/articles
curl -v http://localhost:8080/api/articles?author=author1
curl -v http://localhost:8080/api/search?keyword=title
curl -v -X DELETE --user editor:s3cr3t http://localhost:8080/api/articles/1
After log in you will be able to access Spring Actuator endpoints, e.g. http://localhost:8080/dump for thread dump, health indicators and much more. Full list of endpoints can be found here.
curl --user editor:s3cr3t http://localhost:8080/health
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 117747277824,
"free": 13068156928,
"threshold": 10485760
},
"db": {
"status": "UP",
"database": "HSQL Database Engine",
"hello": 1
}
}
curl --user editor:s3cr3t http://localhost:8080/metrics
{
"mem": 489070,
"mem.free": 210976,
"processors": 4,
"instance.uptime": 596152,
"uptime": 606689,
"systemload.average": 0.84,
"heap.committed": 411648,
"heap.init": 124928,
"heap.used": 200671,
"heap": 1749504,
"nonheap.committed": 79168,
"nonheap.init": 2496,
"nonheap.used": 77427,
"nonheap": 0,
"threads.peak": 23,
"threads.daemon": 21,
"threads.totalStarted": 27,
"threads": 23,
"classes": 10408,
"classes.loaded": 10408,
"classes.unloaded": 0,
"gc.ps_scavenge.count": 10,
"gc.ps_scavenge.time": 363,
"gc.ps_marksweep.count": 2,
"gc.ps_marksweep.time": 181,
"httpsessions.max": -1,
"httpsessions.active": 11,
"datasource.primary.active": 0,
"datasource.primary.usage": 0,
"gauge.response.dump": 98,
"gauge.response.metrics": 5,
"gauge.response.api.articles": 18,
"gauge.response.api.search": 20,
"gauge.response.unmapped": 1,
"gauge.response.api.articles.id": 12,
"counter.status.405.unmapped": 3,
"counter.status.415.api.articles": 1,
"counter.status.200.api.search": 1,
"counter.status.200.metrics": 2,
"counter.status.200.api.articles.id": 1,
"counter.status.200.api.articles": 3,
"counter.status.404.api.articles.id": 1,
"counter.status.200.dump": 1,
"counter.status.201.api.articles": 4,
"counter.status.401.unmapped": 2
}