-
Notifications
You must be signed in to change notification settings - Fork 140
[Rough Cut] Using QBit to create Java RESTful microservices
Before we delve into QBit restful services, let's cover what we get from gradle's application plugin. In order to be a microservice, a service needs to run in a standalone process or a related group of standalone processes.
Building a standalone application with gradle is quite easy. You use the gradle application plug-in.
apply plugin: 'java'
apply plugin:'application'
sourceCompatibility = 1.8
version = '1.0'
mainClassName = "io.advantageous.examples.Main"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
}
To round out this example, let's create a simple Main java class.
package io.advantageous.examples;
public class Main {
public static void main(String... args) {
System.out.println("Hello World!");
}
}
The project structure is as follows:
$ tree
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── restful-qbit.iml
├── settings.gradle
└── src
└── main
└── java
└── io
└── advantageous
└── examples
└── Main.java
We use a standard maven style structure.
To build this application we use the following commands:
$ gradle clean build
:clean UP-TO-DATE
:compileJava
:processResources UP-TO-DATE
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build
BUILD SUCCESSFUL
Total time: 2.474 secs
To run our application we use the following:
$ gradle run
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
Hello World!
BUILD SUCCESSFUL
Total time: 2.202 secs
Learn more about the gradle application plugin here.
There are three more commands that we care about:
- installDist Installs the application into a specified directory
- distZip Creates ZIP archive including libs and start scripts
- distTar Creates TAR archive including libs and start scripts
The application plug-in allows you to create scripts to start a process. These scripts work on all operating systems. Microservices run as standalone processes. The gradle application plug-in is a good fit for microservice development.
Let's use the application plugin to create a dist zip file.
$ gradle distZip
Let's see where gradle put the zip.
$ find . -name "*.zip"
./build/distributions/restful-qbit-1.0.zip
Let's unzip to a directory.
$ mkdir /opt/example
$ unzip ./build/distributions/restful-qbit-1.0.zip -d /opt/example/
Archive: ./build/distributions/restful-qbit-1.0.zip
creating: /opt/example/restful-qbit-1.0/
creating: /opt/example/restful-qbit-1.0/lib/
inflating: /opt/example/restful-qbit-1.0/lib/restful-qbit-1.0.jar
creating: /opt/example/restful-qbit-1.0/bin/
inflating: /opt/example/restful-qbit-1.0/bin/restful-qbit
inflating: /opt/example/restful-qbit-1.0/bin/restful-qbit.bat
Now we can run it from the install directory.
$ /opt/example/restful-qbit-1.0/bin/restful-qbit
Hello World!
Contents of restful-qbit startup script.
$ cat /opt/example/restful-qbit-1.0/bin/restful-qbit
#!/usr/bin/env bash
##############################################################################
##
## restful-qbit start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and RESTFUL_QBIT_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="restful-qbit"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/.." >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/lib/restful-qbit-1.0.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And RESTFUL_QBIT_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $RESTFUL_QBIT_OPTS
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" io.advantageous.examples.Main "$@"
Let's create a simple HTTP service that responsds with pong when we send it a ping as follows.
$ curl http://localhost:9090/services/pongservice/ping
"pong"
First let's import the qbit lib. We are using the SNAPSHOT but hopefully by the time you read this the release will be available.
Add the following to your gradle build.
apply plugin: 'java'
apply plugin:'application'
sourceCompatibility = 1.8
version = '1.0'
mainClassName = "io.advantageous.examples.Main"
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile group: 'io.advantageous.qbit', name: 'qbit-vertx', version: '0.7.3-SNAPSHOT'
}
Define a service as follows:
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
@RequestMapping
public class PongService {
@RequestMapping
public String ping() {
return "pong";
}
}
The @RequestMapping defines the service as one that responsds to an HTTP call. If you do not specify the path, then the lower case name of the class and the lower case name of the method becomes the path. Thus PongService.ping() becomes /pongservice/ping. To bind this service to a port we use a service server. A service server is a server that hosts services like our pong service.
Change the Main class to use the ServiceEndpointServer ServiceEndpointServer ServiceEndpointServer as follows:
package io.advantageous.examples;
import io.advantageous.qbit.server.ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer;
import io.advantageous.qbit.server.EndpointServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer serviceServer = EndpointServerBuilder
.endpointServerBuilder()
.setPort(9090).build();
serviceServer.initServices(new PongService());
serviceServer.startServer();
}
}
Notice we pass an instance of the PongService to the initServices of the service server. If we want to change the root address from "services" to something else we could do this:
final ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer serviceServer = EndpointServerBuilder
.endpointServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(new PongService());
serviceServer.startServer();
Now we can call this using curl as follows:
$ curl http://localhost:9090/main/pongservice/ping
"pong"
QBit uses builders to make it easy to integrate QBit with frameworks like Spring or Guice or to just use standalone.
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
@RequestMapping("/add")
public int add(@RequestParam("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
Notice the above uses @RequestParam this allows you to pull the requests params as arguments to the method. If we pass a URL like: http://localhost:9090/main/my/service/add?a=1&b=2. QBit will use 1 for argument a and 2 for argument b.
package io.advantageous.examples;
import io.advantageous.qbit.server.ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer;
import io.advantageous.qbit.server.EndpointServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer serviceServer = EndpointServerBuilder
.endpointServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(
new PongService(),
new SimpleService());
serviceServer.startServer();
}
}
When we load this URL:
http://localhost:9090/main/my/service/add?a=1&b=2
We get this response.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
3
Many think that URLs without request parameters are more search engine friendly or you can list things under a context which make it more RESTful. This is open to debate. I don't care about debate, but here is an example of using URI params.
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
...
...
@RequestMapping("/add2/{a}/{b}")
public int add2( @PathVariable("a") int a,
@PathVariable("b") int b) {
return a + b;
}
}
Now we can pass arguments which are part of the URL path. We do this by using the @PathVariable
annotation. Thus the following URL:
http://localhost:9090/main/my/service/add2/1/4
The 1 is correlates to the "a" argument to the method and the 4 correlates to the "b" arguments.
We would get the following response when we load this URL.
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
5
You can mix and match URI params and request params.
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
...
...
@RequestMapping("/add3/{a}/")
public int add3( @PathVariable("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
This allows us to mix URI params and request params as follows:
http://localhost:9090/main/my/service/add3/1?b=8
Now we get this response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 1
9
Let's create a simple Employee / Department listing application.
package io.advantageous.examples.employees;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestMethod;
import io.advantageous.qbit.annotation.RequestParam;
import java.util.*;
import java.util.function.Predicate;
@RequestMapping("/dir")
public class EmployeeDirectoryService {
private final List<Department> departmentList = new ArrayList<>();
@RequestMapping("/employee/{employeeId}/")
public Employee listEmployee(@PathVariable("employeeId") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get().employeeList()
.stream().filter(employee -> employee.getId() == employeeId)
.findFirst().get();
} else {
return null;
}
}
@RequestMapping("/department/{departmentId}/")
public Department listDepartment(@PathVariable("departmentId") final long departmentId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get();
} else {
return null;
}
}
@RequestMapping(value = "/department/", method = RequestMethod.POST)
public boolean addDepartment( @RequestParam("departmentId") final long departmentId,
@RequestParam("name") final String name) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department " + departmentId + " already exists");
}
departmentList.add(new Department(departmentId, name));
return true;
}
@RequestMapping(value = "/department/employee/", method = RequestMethod.POST)
public boolean addEmployee( @RequestParam("departmentId") final long departmentId,
final Employee employee) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (!departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department not found");
}
final boolean alreadyExists = departmentOptional.get().employeeList().stream()
.anyMatch(employeeItem -> employeeItem.getId() == employee.getId());
if (alreadyExists) {
throw new IllegalArgumentException("Employee with id already exists " + employee.getId());
}
departmentOptional.get().addEmployee(employee);
return true;
}
}
To add three departments Engineering, HR and Sales:
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=1&name=Engineering
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=2&name=HR
$ curl -X POST http://localhost:9090/main/dir/department/?departmentId=3&name=Sales
Now let's add some employees into those departments.
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Rick","lastName":"Hightower", "id": 1}' /
http://localhost:9090/main/dir/departme\
nt/employee/?departmentId=1
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Diana","lastName":"Hightower", "id": 2}' /
http://localhost:9090/main/dir/departm\
ent/employee/?departmentId=2
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Maya","lastName":"Hightower", "id": 3}' /
http://localhost:9090/main/dir/departme\
nt/employee/?departmentId=3
curl -H "Content-Type: application/json" -X POST /
-d '{"firstName":"Paul","lastName":"Hightower", "id": 4}' /
http://localhost:9090/main/dir/departmen\
t/employee/?departmentId=3
Now let's list the employees. We can get the employees with the following curl.
$ curl http://localhost:9090/main/dir/employee/1
{"firstName":"Rick","lastName":"Hightower","id":1}
$ curl http://localhost:9090/main/dir/employee/2
{"firstName":"Diana","lastName":"Hightower","id":2}
$ curl http://localhost:9090/main/dir/employee/3
{"firstName":"Maya","lastName":"Hightower","id":3}
$ curl http://localhost:9090/main/dir/employee/4
{"firstName":"Paul","lastName":"Hightower","id":4}
Now we can list departments with our RESTful API:
$ curl http://localhost:9090/main/dir/department/1
{"id":1,"employees":[{"firstName":"Rick","lastName":"Hightower","id":1}]}
$ curl http://localhost:9090/main/dir/department/2
{"id":2,"employees":[{"firstName":"Diana","lastName":"Hightower","id":2}]}
$ curl http://localhost:9090/main/dir/department/3
{
"id": 3,
"employees": [
{
"firstName": "Maya",
"lastName": "Hightower",
"id": 3
},
{
"firstName": "Paul",
"lastName": "Hightower",
"id": 4
}
]
}
Some feel that is a search engine friendly RESTful interface although it is not. A true RESTful interface would have hyperlinks, but let's leave that off for another discussion lest we bring on the debate akin to vi vs. emacs or Scala vs. Groovy.
Generally speaking people prefer the following (subject to much debate):
- POST to add to a resource
- PUT to update a resource
- GET to read a resource
- DELETE to remove an item from a list
- End a group in the singular form with a slash as in department/
- Use the name or id in the URI path to address a resource department/1/ would address the department with id 1.
You can use any of the HTTP verbs. Typically as mentioned before you use the DELETE verb to delete a resource as follows:
@RequestMapping(value = "/employee", method = RequestMethod.DELETE)
public boolean removeEmployee(@RequestParam("id") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
@RequestMapping(value = "/department", method = RequestMethod.DELETE)
public boolean removeDepartment(@RequestParam("id") final long departmentId) {
return departmentList.removeIf(department -> departmentId == department.getId());
}
Now let's delete somebody.
curl -H "Content-Type: application/json" -X DELETE /
http://localhost:9090/main/dir/employee?id=3
You can mix and match @PathVariable
and @RequestParam
which is a quite common case and one that QBit just started supporitng this last release.
@RequestMapping(value = "/department/{departmentId}/employee", method = RequestMethod.DELETE)
public boolean removeEmployeeFromDepartment(
@PathVariable("departmentId") final long departmentId,
@RequestParam("id") final long employeeId) {
/* Find the department by id. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
In this very common case, you can use the PathVariable to address the department resource and then ask for a specific employee to be deleted only from this department.
curl -H "Content-Type: application/json" -X DELETE \
http://localhost:9090/main/dir/department/3/employee?id=4
$ tree
.
├── addDepartments.sh
├── addEmployees.sh
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── removeEmployee.sh
├── restful-qbit.iml
├── settings.gradle
├── showEmployees.sh
└── src
├── main
│ └── java
│ └── io
│ └── advantageous
│ └── examples
│ ├── Main.java
│ ├── PongService.java
│ ├── SimpleService.java
│ └── employees
│ ├── Department.java
│ ├── Employee.java
│ └── EmployeeDirectoryService.java
└── test
└── java
└── io
└── advantageous
└── examples
└── employees
└── EmployeeDirectoryServiceTest.java
package io.advantageous.examples.employees;
import java.util.ArrayList;
import java.util.List;
public class Department {
private String name;
private final long id;
private final List<Employee> employees = new ArrayList();
public Department(long id, String name) {
this.id = id;
this.name = name;
}
public void addEmployee(final Employee employee) {
employees.add(employee);
}
public boolean removeEmployee(final long id) {
return employees.removeIf(employee -> employee.getId() == id);
}
public List<Employee> employeeList() {
return employees;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package io.advantageous.examples.employees;
public class Employee {
private String firstName;
private String lastName;
private final long id;
public Employee(String firstName, String lastName, long id) {
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public long getId() {
return id;
}
}
package io.advantageous.examples.employees;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestMethod;
import io.advantageous.qbit.annotation.RequestParam;
import java.util.*;
import java.util.function.Predicate;
@RequestMapping("/dir")
public class EmployeeDirectoryService {
private final List<Department> departmentList = new ArrayList<>();
@RequestMapping("/employee/{employeeId}/")
public Employee listEmployee(@PathVariable("employeeId") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Find employee in department. */
if (departmentOptional.isPresent()) {
return departmentOptional.get().employeeList()
.stream().filter(employee -> employee.getId() == employeeId)
.findFirst().get();
} else {
return null;
}
}
@RequestMapping("/department/{departmentId}/")
public Department listDepartment(@PathVariable("departmentId") final long departmentId) {
return departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst().get();
}
@RequestMapping(value = "/department/", method = RequestMethod.POST)
public boolean addDepartment( @RequestParam("departmentId") final long departmentId,
@RequestParam("name") final String name) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department " + departmentId + " already exists");
}
departmentList.add(new Department(departmentId, name));
return true;
}
@RequestMapping(value = "/department/employee/", method = RequestMethod.POST)
public boolean addEmployee( @RequestParam("departmentId") final long departmentId,
final Employee employee) {
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findAny();
if (!departmentOptional.isPresent()) {
throw new IllegalArgumentException("Department not found");
}
final boolean alreadyExists = departmentOptional.get().employeeList().stream()
.anyMatch(employeeItem -> employeeItem.getId() == employee.getId());
if (alreadyExists) {
throw new IllegalArgumentException("Employee with id already exists " + employee.getId());
}
departmentOptional.get().addEmployee(employee);
return true;
}
@RequestMapping(value = "/employee", method = RequestMethod.DELETE)
public boolean removeEmployee(@RequestParam("id") final long employeeId) {
/* Find the department that has the employee. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.employeeList().stream()
.anyMatch(employee -> employee.getId() == employeeId)).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
@RequestMapping(value = "/department", method = RequestMethod.DELETE)
public boolean removeDepartment(@RequestParam("id") final long departmentId) {
return departmentList.removeIf(department -> departmentId == department.getId());
}
@RequestMapping(value = "/department/{departmentId}/employee", method = RequestMethod.DELETE)
public boolean removeEmployeeFromDepartment(
@PathVariable("departmentId") final long departmentId,
@RequestParam("id") final long employeeId) {
/* Find the department by id. */
final Optional<Department> departmentOptional = departmentList.stream()
.filter(department -> department.getId() == departmentId).findFirst();
/* Remove employee from department. */
if (departmentOptional.isPresent()) {
departmentOptional.get().removeEmployee(employeeId);
return true;
} else {
return false;
}
}
}
package io.advantageous.examples;
import io.advantageous.qbit.annotation.RequestMapping;
@RequestMapping
public class PongService {
@RequestMapping
public String ping() {
return "pong";
}
}
####SimpleService.java
package io.advantageous.examples;
import io.advantageous.qbit.annotation.PathVariable;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.RequestParam;
@RequestMapping("/my/service")
public class SimpleService {
@RequestMapping("/add")
public int add(@RequestParam("a") int a,
@RequestParam("b") int b) {
return a + b;
}
@RequestMapping("/add2/{a}/{b}")
public int add2( @PathVariable("a") int a,
@PathVariable("b") int b) {
return a + b;
}
@RequestMapping("/add3/{a}/")
public int add3( @PathVariable("a") int a,
@RequestParam("b") int b) {
return a + b;
}
}
####Main.java
package io.advantageous.examples;
import io.advantageous.examples.employees.EmployeeDirectoryService;
import io.advantageous.qbit.server.ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer;
import io.advantageous.qbit.server.EndpointServerBuilder;
public class Main {
public static void main(String... args) {
final ServiceEndpointServer
ServiceEndpointServer
ServiceEndpointServer serviceServer = EndpointServerBuilder
.endpointServerBuilder()
.setUri("/main")
.setPort(9090).build();
serviceServer.initServices(
new PongService(),
new SimpleService(),
new EmployeeDirectoryService());
serviceServer.startServer();
}
}
QBit Website What is Microservices Architecture?
QBit Java Micorservices lib tutorials
The Java microservice lib. QBit is a reactive programming lib for building microservices - JSON, HTTP, WebSocket, and REST. QBit uses reactive programming to build elastic REST, and WebSockets based cloud friendly, web services. SOA evolved for mobile and cloud. ServiceDiscovery, Health, reactive StatService, events, Java idiomatic reactive programming for Microservices.
Reactive Programming, Java Microservices, Rick Hightower
Java Microservices Architecture
[Microservice Service Discovery with Consul] (http://www.mammatustech.com/Microservice-Service-Discovery-with-Consul)
Microservices Service Discovery Tutorial with Consul
[Reactive Microservices] (http://www.mammatustech.com/reactive-microservices)
[High Speed Microservices] (http://www.mammatustech.com/high-speed-microservices)
Reactive Microservices Tutorial, using the Reactor
QBit is mentioned in the Restlet blog
All code is written using JetBrains Idea - the best IDE ever!
Kafka training, Kafka consulting, Cassandra training, Cassandra consulting, Spark training, Spark consulting
Tutorials
- QBit tutorials
- Microservices Intro
- Microservice KPI Monitoring
- Microservice Batteries Included
- RESTful APIs
- QBit and Reakt Promises
- Resourceful REST
- Microservices Reactor
- Working with JSON maps and lists
__
Docs
Getting Started
- First REST Microservice
- REST Microservice Part 2
- ServiceQueue
- ServiceBundle
- ServiceEndpointServer
- REST with URI Params
- Simple Single Page App
Basics
- What is QBit?
- Detailed Overview of QBit
- High level overview
- Low-level HTTP and WebSocket
- Low level WebSocket
- HttpClient
- HTTP Request filter
- HTTP Proxy
- Queues and flushing
- Local Proxies
- ServiceQueue remote and local
- ManagedServiceBuilder, consul, StatsD, Swagger support
- Working with Service Pools
- Callback Builders
- Error Handling
- Health System
- Stats System
- Reactor callback coordination
- Early Service Examples
Concepts
REST
Callbacks and Reactor
Event Bus
Advanced
Integration
- Using QBit in Vert.x
- Reactor-Integrating with Cassandra
- Using QBit with Spring Boot
- SolrJ and service pools
- Swagger support
- MDC Support
- Reactive Streams
- Mesos, Docker, Heroku
- DNS SRV
QBit case studies
QBit 2 Roadmap
-- Related Projects
- QBit Reactive Microservices
- Reakt Reactive Java
- Reakt Guava Bridge
- QBit Extensions
- Reactive Microservices
Kafka training, Kafka consulting, Cassandra training, Cassandra consulting, Spark training, Spark consulting