Skip to content

Latest commit

 

History

History
503 lines (377 loc) · 15 KB

File metadata and controls

503 lines (377 loc) · 15 KB

quarkus-vs-springboot-reactive-rest-api

Comparison between Quarkus and Spring Boot using a simple Reactive RestFul Api.

Contents

  1. Requirements
  2. Tech Stack
  3. Build and Run
  4. Swagger UI
  5. Quarkus vs Spring Boot Comparision
    1. RestFul API Support
    2. Reactive Programming Support
    3. MongoDb Reactive Support
    4. Swagger Support
    5. Testing
    6. Metrics - Prometheus
    7. Docker Build
    8. Performance Test
    9. Final Results
  6. Docker Compose
  7. Jmeter
  8. Kubernetes/Istio
  9. Naive Stress Test
  10. References

Requirements

To compile and run this project you will need:

  • JDK 8+
  • Docker
  • Mongodb
  • Maven 3.6.3
  • Keystore(or use auth profile to generate one automatic)

Tech Stack

  • Reactive RestFul API
  • MongoDB
  • Security(JWT)
  • Swagger

Build and Run

On the root folder compile the source code for both projects.

mvn clean compile
  • Run

If you are not using auth profile you will need to place a public key in /tmp/publicKey.pem or setting path PUBLIC_KEY_PATH.

  • Using auth profile

If you are using auth profile it will generate a keystore(if not specified in PRIVATE_KEY_PATH and PUBLIC_KEY_PATH) and expose endpoint /api/auth.

QUARKUS_PROFILE=auth mvn compile quarkus:dev

or

mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=auth"
  • Quarkus

Quarkus has a nice hot deployment feature that you can easily develop on the fly without restarting the service to recompile the classes, that's kind of unusual for Java but very common feature for interpreted languages like PHP/Python.

cd quarkus
mvn compile quarkus:dev

PS: To change default port(8081) set QUARKUS_HTTP_PORT.

  • Spring Boot

Spring Boot has also hot deployment feature but need some interaction from the IDE, more details look at Hot Swapping.

cd spring-boot
mvn spring-boot:run

PS: To change default port(8080) set mvn spring-boot:run -Dspring-boot.run.arguments="--server.port={PORT}".

Swagger UI

To access Swagger UI and generate a valid JWT use /api/auth when auth profile is on.

Use following roles:

  • ROLE_ADMIN - Access for all endpoints
  • ROLE_COMPANY_READ - Read Access to GET - /api/companies and GET - /api/companies/{id}.
  • ROLE_COMPANY_CREATE - Create Access to POST - /api/companies
  • ROLE_COMPANY_SAVE - Update Access to PUT - /api/companies
  • ROLE_COMPANY_DELETE - Delete Access to DELETE - /api/companies

PS: To generate a JWT first need to Logout on Authorize Button.

Quarkus vs Spring Boot Comparision

  • RestFul API Support

  • Quarkus

Quarkus has support for JAX-RS with RESTEasy framework also Spring Web Annotations.

Click here to expand...

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/api/example")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class ExampleResource { 
    @GET
    public Response get() {return Response.ok().entity(something).build(); }     
}     

  • Spring Boot

Spring also supports JAX-RS but more commonly used is with Spring Web Annotations.

Click here to expand...

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/example")
public class ExampleController {
    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> get() {
        return ResponseEntity.ok(something);
    }
}

  • Reactive Programming Support

  • Quarkus

Quarkus is reactive by default using Vert.x under the hood, the reactive rest support is using Mutiny also has support for others libraries like RxJava and Reactor.

Click here to expand...

public class CompanyResource {
    public Multi<Company> getAllActiveCompanies() {
        return Company.streamAll();
    }     

    public Uni<Response> getById(@PathParam("id") String id) {
         return Company.findById(new ObjectId(id))
             .onItem().ifNull().failWith(NotFoundException::new)
             .map(c -> Response.ok(c).build());         
    }
}

Full example look at CompanyResource.

  • Spring Boot

Spring uses Reactor for reactive programming but also other libraries like RxJava.

Click here to expand...

public class CompanyController {
    public Flux<Company> getAllActiveCompanies() {
        return companyRepository.findAll();
    }

    public Mono<Company> getById(@PathParam("id") String id) {
        return companyRepository.findById(id)
            .flatMap(Mono::just)
            .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
    }
}

Full example look at CompanyController.

  • Mongodb Reactive Support

  • Quarkus

Quarkus uses Panache and provides Active Record Pattern style and repository, it's a nice library but has a preview and is not backward compatibility.

Click here to expand...

@MongoEntity(collection = "quarkus_companies")
public class Company extends ReactivePanacheMongoEntity implements Serializable {
    @NotBlank
    public String name;
    public boolean activated;
    
    public static Multi<Company> findActiveCompanies(Integer pageSize) {
        return find("activated", true)
              .page(Page.ofSize(pageSize))
              .stream();
    }
}

  • Spring Boot

Spring uses Spring Data and repository style but also support other libraries like RxJava.

Click here to expand...

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "springboot_companies")
public class Company implements Serializable {
    @Id
    private String id;
    private String name;
    private Boolean activated;
}

@Repository
public interface CompanyRepository extends ReactiveMongoRepository<Company, String> {
    @Query("{'activated': true}")
    Flux<Company> findActiveCompanies(final Pageable page);
}

  • Swagger Support

  • Quarkus

Quarkus has a good support for OpenApi and Swagger UI just needed library quarkus-smallrye-openapi and a few annotations.

Click here to expand...

@OpenAPIDefinition(tags = {@Tag(name="company", description="Operations related to companies")})
@SecuritySchemes(@SecurityScheme(securitySchemeName = "api_key",
		type = SecuritySchemeType.APIKEY,
		apiKeyName = "Authorization",
		in = SecuritySchemeIn.HEADER)
)
public class ResourceApplication extends Application {
}

Full example look at ResourceApplication.

  • Spring Boot

Spring Boot has some issues using WebFlux with Swagger UI and no default support for OpenApi. To achieve Swagger UI was necessary to use SpringFox SNAPSHOT version.

  • Testing

  • Quarkus

Quarkus basic has @QuarkusTest annotation and recommends to use rest-assured but had some issues to test reactive rest apis with security(jwt). Had some issues also to test Embedded MongoDb needed to add a class to start mongodb manually.

Click here to expand...

public class MongoTestResource implements QuarkusTestResourceLifecycleManager {
	private static MongodExecutable MONGO;

	private static final Logger LOGGER = Logger.getLogger(MongoTestResource.class);

	@Override
	public Map<String, String> start() {
		//StartMongoDb
	}
}

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
public class CompanyResourceTest {
}

Full example look at MongoTestResource and CompanyResourceTest.

  • Spring Boot

Spring Boot has a good testing api using @SpringBoot, @DataMongoTest and @WebFluxTest, more details look at spring tests.

  • Metrics(Prometheus)

  • Quarkus

Quarkus uses Microprofile annotations like @Timed, @Metered, @Counted but need to add it manually for each endpoint, more details look at CompanyResource.

  • Spring Boot

Spring Boot uses micrometer and has easy integration with prometheus just need library micrometer-registry-prometheus.

  • Docker Build

  • Quarkus

It has by default support to build a docker image and a native image also. To build a docker image:

cd quarkus
mvn clean package -Dquarkus.container-image.build=true
docker build -f src/main/docker/Dockerfile.jvm -t quarkus .

To build native image

mvn package -Pnative -Dquarkus.native.container-build=true
docker build -f src/main/docker/Dockerfile.native -t quarkus .

PS: The native image didn't work for me using laptop i7 16GB dell inpiron.

  • Spring Boot

Spring Boot doesn't have a built-in docker image but can easily use jib or fabric8. To build a docker image using fabric8 plugin:

cd spring-boot
mvn clean install docker:build
  • Performance Test

I've used 2 replicas for each service and deployed pods to GKE.

  • Boot Time

  • Quarkus

Quarkus is really fast to startup as you can see in attached images below, it took less than 5 seconds. Replica 1

Replica_1

Replica 2

Replica_2

  • Spring Boot

Spring Boot is not so fast as Quarkus but also not too slow it took average 23 seconds to startup. Replica 1

Replica_1

Replica 2

Replica_2

  • Stress 100 tps per sec

  • Quarkus

  • Quarkus was slower than Spring Boot to handle the requests but I believe if I change the connection pool would perform better. Performance_1 Performance_2 Performance_3

  • Spring Boot

  • Spring Boot handle the requests faster Performance_1 Performance_2 Performance_3

  • Stress 500 tps per sec(10 times)

I've also tested with 3 replicas and had similar results.

  • Quarkus

3 Replicas

Quarkus_3_Replicas

Boot Time

Boot_Time

Result

Performance_1 Performance_2 Performance_3

  • Spring Boot

3 Replicas

SpringBoot_3_Replicas

Result

Performance_1 Performance_2 Performance_3

  • Final Results

Topics Winner - IMHO :)
RestFul API Support draw
Reactive Programming Support
MongoDb Reactive Support
Swagger Support
Testing
Metrics - Prometheus
Docker Build
Performance Test

Docker Compose

Once the docker images are build you can use docker-compose to run both services with mongodb, prometheus and grafana.

Jmeter

Check jmeter folder to run performance tests and see the results for both services.

Kubernetes/Istio

Check k8s folder for kubernetes/minikube and istio setup.

cd docker-compose
docker-compose up -d

Naive Stress Testing cURL

  • Create
./naive-stress-test.sh -c 1 -r 1 \
-a localhost:8080/api/companies \
-X POST \
-H "Authorization: bearer XXXX" \
-H "Content-Type: application/json" \
-d '{"name": "test curl", "activated": true}'
  • Create appending body with microseconds
./naive-stress-test.sh -c 1 -r 1 \
-a localhost:8080/api/companies \
-X POST \
-H "Authorization: bearer XXXX" \
-H "Content-Type: application/json" \
-d '{"name": "test curl - #INCREMENT_TIMESTAMP", "activated": true}'
  • Get
./naive-stress-test.sh -c 1 -r 1 \
-a localhost:8080/api/companies \
-X GET \
-H "Authorization: bearer XXXX" \
-H "Content-Type: application/json"

References