Think Reactive and Native With Quarkus, Kotlin, and GraphQL
Native applications are an essential factor in containerization. Reactive programming is an excellent choice for developing stream-based or queue-based applications
Join the DZone community and get the full member experience.
Join For FreeIntroduction
In today’s world of software development, the terms “native” and “reactive” have gained significant popularity, becoming crucial considerations for developers, architects, and businesses alike.
Regardless of whether you’re building front-end applications or back-end systems, the native and reactive approach has become a crucial component of modern software development’s non-functional requirements. This blog aims to explore the significance of these two concepts and why they have become so essential.
We will also take a closer look at where these two concepts will fit in the three main categories of prevalent software solutions in the IT industry today: Request-Response (Synchronous), queue-based (Asynchronous), and event-based (Streams/Event Sourcing). Finally, we will examine how native and reactive architectures can bring significant benefits to each of these categories.
About “Native”
Containerization has become a popular approach to deploying applications in modern cloud environments. With containers, applications are packaged into a single container image, which can then be deployed across different environments consistently. However, when it comes to running applications in containers, there are two main options: running as a traditional virtual machine (VM) or running as a native application. Native applications are built specifically for the target platform, resulting in better performance, faster startup times, lower memory consumption, and better runtime performance. They can be easily scaled horizontally, increasing resource utilization efficiency. Additionally, running applications natively reduces the attack surface area, making the application more secure. In summary, native applications are an essential factor in containerization, providing better performance, scalability, resource efficiency, and security, and are crucial for the success of modern cloud environments. You can learn more about Cloud-Native here.
About “Reactive”
Reactive programming has gained popularity in recent years, and for good reason. One significant benefit of reactive programming is its ability to handle stream-based or queue-based applications. In such applications, the processing of incoming messages or events is time-sensitive and requires immediate action. Reactive programming provides a solution for these scenarios by allowing the developer to write applications that can react immediately to incoming events rather than waiting for a response from the previous event. This means that the application can handle high volumes of incoming data without blocking, resulting in a faster and more efficient application. Additionally, reactive programming allows for better scalability and resilience, as it can handle large amounts of incoming data without overloading the system. In summary, reactive programming is an excellent choice for developing stream-based or queue-based applications, as it allows for immediate response and efficient handling of high volumes of data, resulting in a more scalable, resilient, and performant application.
Tech Stack
Quarkus is an emerging alternative to the Spring framework from RedHat. It is rapidly gaining popularity due to its high performance and efficient resource usage. One of the key reasons for its popularity is its ability to use a compile-time approach to build native apps, resulting in lightweight and fast apps. This approach, coupled with Quarkus’ support for GraalVM, allows Quarkus apps to consume minimal resources, making them ideal for running in resource-constrained environments like containers or serverless environments.
Quarkus also provides a streamlined development experience for developers, with extensive development tools and hot reloading support. Quarkus is designed to work well with cloud-native architectures and provides out-of-the-box support for several cloud-native technologies like Kubernetes, Istio, and Knative.
Quarkus Reactive is a powerful framework for building reactive applications, with features like Panache that make it easy to work with databases like MySQL using plain old Java objects (POJOs). With Panache, developers can create entities that map to database tables and perform CRUD operations using simple, type-safe methods.
In summary, Quarkus is an excellent alternative to the Spring framework, providing several advantages such as high performance, efficient resource usage, cloud-native support, and a streamlined development experience, making it an ideal choice for modern cloud-native architectures.
Kotlin: is a statically typed programming language that was designed to be more concise and expressive than Java while still being fully interoperable with Java code. Developed by JetBrains, Kotlin is open source and has rapidly gained popularity due to its ease of use, concise syntax, and improved developer productivity. Kotlin is used for developing a wide range of applications, from Android mobile apps to server-side applications and beyond. Its adoption has been driven by major companies like Google, Netflix, and Pinterest, making Kotlin a popular choice among developers for modern software development.
GraphQL: Developed by Facebook, GraphQL is a query language for APIs that has since been made available to the wider developer community. It offers a more efficient, powerful, and flexible option compared to REST for modern web APIs. With GraphQL, you can specify the exact data you need, reducing issues of over-fetching and under-fetching. Key features are Mutations and GraphQL-UI that come along with your API.
Demo Scene
Suppose that AWS Cloud does not offer its crucial “Reliability” pillar, and you are responsible for managing your app (EC2 instances/pods) independently. Your task is to create an API that is reactive, which means you need to receive real-time notifications when a pod is terminated or created. Let us develop the API to fulfill these essential requirements.
— Even if it’s just a hypothetical scenario, it can still be alarming to consider the potential challenges and risks that may arise in such a situation
Database Schema
Define the main components of AWS Network like VPC, Region, Subnet, EC2, etc.
CREATE TABLE region (
region_id INT NOT NULL AUTO_INCREMENT,
region_name VARCHAR(255) NOT NULL,
PRIMARY KEY (region_id)
);
CREATE TABLE vpc (
vpc_id INT NOT NULL AUTO_INCREMENT,
vpc_name VARCHAR(255) NOT NULL,
region_id INT NOT NULL,
PRIMARY KEY (vpc_id),
FOREIGN KEY (region_id) REFERENCES region(region_id)
);
CREATE TABLE azone(
az_id INT NOT NULL AUTO_INCREMENT,
az_name VARCHAR(255) NOT NULL,
region_id INT NOT NULL,
PRIMARY KEY (az_id),
FOREIGN KEY (region_id) REFERENCES region(region_id)
);
CREATE TABLE subnet (
subnet_id INT NOT NULL AUTO_INCREMENT,
subnet_name VARCHAR(255) NOT NULL,
vpc_id INT NOT NULL,
az_id INT NOT NULL,
PRIMARY KEY (subnet_id),
FOREIGN KEY (vpc_id) REFERENCES vpc(vpc_id),
FOREIGN KEY (az_id) REFERENCES azone(az_id)
);
CREATE TABLE ec2 (
ec2_id INT NOT NULL AUTO_INCREMENT,
ec2_name VARCHAR(255) NOT NULL,
subnet_id INT NOT NULL,
PRIMARY KEY (ec2_id),
FOREIGN KEY (subnet_id) REFERENCES subnet(subnet_id)
);
Now, Let’s create a Quarkus Project by using IntelliJ Idea’s Project Initializer, or you can simply visit the official website and create it.
Define Your Entity Classes
@Entity
@Cacheable
@Table(schema = "sysopsdb", name="azone")
class Azone {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="az_id")
var id: Long? = null
@Column(name ="az_name")
lateinit var name: String
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "region_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
var region: Region? = null
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, mappedBy = "azone")
var subnets: Set<Subnet> = HashSet<Subnet>()
}
@Entity
@Cacheable
@Table(schema = "sysopsdb", name="ec2")
class EC2{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="ec2_id")
var id: Long? = null
@Column(name ="ec2_name")
lateinit var name: String
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "subnet_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
var subnet: Subnet? = null
}
@Entity
@Cacheable
@Table(schema = "sysopsdb", name="region")
class Region {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="region_id")
var id: Long? = null
@Column(name ="region_name")
lateinit var name: String
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, mappedBy = "region")
val azs: Set<Azone> = HashSet<Azone>()
}
@Entity
@Cacheable
@Table(schema = "sysopsdb", name="subnet")
class Subnet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="subnet_id")
var id: Long? = null
@Column(name ="subnet_name")
lateinit var name: String
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "vpc_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
val vpc: VPC? = null
@ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "az_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
val azone: Azone? = null
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, mappedBy = "subnet")
val ec2s: Set<EC2> = HashSet<EC2>()
}
@Entity
@Cacheable
@Table(schema ="sysopsdb", name = "vpc")
class VPC {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="vpc_id")
var id: Long? = null
@Column(name ="vpc_name")
lateinit var name: String
}
Panache Repositories
@ApplicationScoped
class AzoneRepository: PanacheRepository<Azone> {
fun findByName(name: String) = find("name", name).firstResult<Azone>()
fun deleteInstance(id: Long) = delete("id", id)
}
@ApplicationScoped
class EC2Repository: PanacheRepository<EC2> {
fun findByName(name: String) = find("name", name).firstResult<EC2>()
fun deleteInstance(id: Long) = delete("id", id)
}
@ApplicationScoped
class RegionRepository: PanacheRepository<Region> {
fun findByName(name: String) = find("name", name).firstResult<Region>()
fun deleteInstance(id: Long) = delete("id", id)
}
@ApplicationScoped
class SubnetRepository: PanacheRepository<Subnet> {
fun findByName(name: String) = find("name", name).firstResult<Subnet>()
fun deleteInstance(id: Long) = delete("id", id)
}
@ApplicationScoped
class VPCRepository: PanacheRepository<VPC> {
fun findByName(name: String) = find("name", name).firstResult<VPC>()
fun deleteInstance(id: Long) = delete("id", id)
}
Service Class
fun listAllEC2Instances(): Uni<List<EC2>> = ec2Instance.listAll()
fun listAllAZs(): Uni<List<Azone>> = azRepository.listAll()
fun listAllRegions(): Uni<List<Region>> = regionRepository.listAll()
fun listAllSubnets(): Uni<List<Subnet>> = subnetRepository.listAll()
fun listAllVpcs(): Uni<List<VPC>> = vpcRepository.listAll()
@Transactional
fun saveEC2Insance(newEC2: EC2, subnetId: Long):Uni<EC2> {
return subnetRepository.findById(subnetId)
.onItem().ifNull().failWith(IllegalArgumentException("Invalid subnet id"))
.onItem().transformToUni { subnet -> newEC2.subnet = subnet; Uni.createFrom().item(newEC2) }
.onItem().transformToUni { it -> ec2Instance.persistAndFlush(it) }
.onItem().invoke { it ->
LoggerFactory.getLogger(javaClass).info("EC2 instance with id ${it.id} created")
}
}
@Transactional
fun deleteEC2Instance(id: Long): Uni<Boolean> {
return ec2Instance.deleteById(id)
.onItem().invoke { it ->
LoggerFactory.getLogger(javaClass).info("EC2 instance with id $id deleted")
}
.onItem().transform { true }
}
Resource Class
@GraphQLApi
@ApplicationScoped
class ClusterResource {
@Inject
lateinit var clusterService: ClusterService
@Query("listAllEC2s")
fun listAllEC2Instances(): Uni<List<EC2>> = clusterService.listAllEC2Instances()
@Query("listAllAZs")
fun listAllAZs(): Uni<List<Azone>> = clusterService.listAllAZs()
@Query("listAllRegions")
fun listAllRegions(): Uni<List<Region>> = clusterService.listAllRegions()
@Query("listAllSubnets")
fun listAllSubnets(): Uni<List<Subnet>> = clusterService.listAllSubnets()
@Query("listAllVPCs")
fun listAllVPCs(): Uni<List<VPC>> = clusterService.listAllVpcs()
@Mutation("addEC2Instance")
fun addEC2Instance(ec2: EC2, subnetId: Long): Uni<EC2> = clusterService.saveEC2Insance(ec2, subnetId)
@Mutation("deleteEC2Instance")
fun deleteEC2Instance(instanceId: Long): Uni<Boolean> = clusterService.deleteEC2Instance(instanceId)
}
Finally
You can Run the App and access this URL.
The GraphQL user interface provides a range of convenient pre-built functionalities, including a command history feature, the ability to list available operations, and an autocomplete feature, among others.
Summary
In this article, we have explored one of the solutions approaches for building Reactive and Native applications, acknowledging that each solution has its own trade-offs and benefits, and we must choose based on our priorities. We have highlighted the high performance and cloud-native support offered by Quarkus and suggested further exploration of these features. Additionally, we have discussed how GraphQL complements this solution by providing many ready-to-use features that make development faster and support low-code, no-code, and open API features. Finally, we have mentioned Kotlin as another excellent technology that offers null safety and eliminates boilerplate code.
The source code can be found on my GitHub. Also, you can reach out to me on LinkedIn for any questions or suggestions.
That’s all for now. Happy Learning!
Opinions expressed by DZone contributors are their own.
Comments