云原⽣quarkus框架项⽬实践
写在前⾯,  不知不觉上篇⽂章已经是好⼏年前了,  回到博客园倍感亲切.  总想写点什么,  发现博客园⾥关于quarkus的⽂章不多,  故把⾃⼰在项⽬过程中的点滴整理如下,  希望对您有所帮助.
⼀、quarkus 是什么?为什么要⽤quarkus
quarkus是Redhat开源的云原⽣微服务框架,  相⽐较成熟的SpringCloud, 为什么要⽤quarkus?
主要有以下⼏点原因:
1. Spring系列框架臃肿、复杂, 更像是⼀个全家桶. ⽽quarkus 简单、⾼效, ⼯具先进
2. 启动速度, quarkus可以在5秒内启动, ⽽spring对于⼀个golang开发者来说, 这个速度直接⽆法忍受.
3. quarkus可以热编译, ⽆需⼿动编译和重启服务, ⽽Spring的热编译..
4. 与其他⼯具集成, Spring集成了⼤部分的⼯具, 但你把DI换成guice试试, quarkus可以很⽅便的集成⼯具, 虽然框架本⾝包含的东西不多
5. quarkus不依赖tomcat或jetty, 可以编译为原⽣应⽤, 性能⼤幅提⾼
6. quarkus耦合低, 项⽬结构⼲净, 适合使⽤代码⽣成器.
⼆、创建⼀个quarkus项⽬
您可以使⽤maven或gradle来快速创建⼀个quarkus项⽬, 具体⽅法见quarkus⽹站, quarkus 只需要创建⼀个Resource类, 就可以启动服务. 零配置.
另外:quarkus 对Kotlin⽀持极为友好,  本⽂将创建⼀个使⽤Kotlin+Gradle的项⽬.  项⽬的配置⽂件: adle.kts内容如下:
plugins{
java
kotlin("jvm") version ("1.3.72")
kotlin("plugin.allopen") version ("1.3.72")
id("io.quarkus") version("1.4.2.Final")
}
allOpen {
annotation("t.ApplicationScoped")
annotation("t.RequestScoped")
}
repositories {
maven("maven.aliyun/nexus/content/groups/public/")
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib"))
implementation("io.quarkus:quarkus-kotlin:1.4.2.Final")
implementation("io.quarkus:quarkus-resteasy:1.4.2.Final")
implementation("io.quarkus:quarkus-resteasy-jsonb:1.4.2.Final")golang kotlin
testImplementation("io.quarkus:quarkus-junit5:1.4.2.Final")
}
tasks.withType<Test> {
useJUnitPlatform()
}
// 代码⽣成器
exec{
workingDir("./tto")
commandLine("sh","-c","./tto.sh")
}
}
tasks.withType<JavaCompile>().configureEach {
optionspilerArgs = listOf("-Xdoclint:none", "-Xlint:none", "-nowarn")
}
三、配置并启动项⽬
您可以创建⼀个类, 并添加注解:@ApplicationScoped , 作为系统启动类,  代码如下:
@ApplicationScoped
class Application {
fun onStart(@Observes event: StartupEvent?) {
println("app started..")
}
}
这并不是必须的,  因为上⽂提到了,  可能需要集成其他⼯具. 接着我们创建⼀个服务如下:import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.MediaType
@Path("/hello")
class HelloResource {
@GET@Path("/{name}")
@Produces(MediaType.APPLICATION_JSON)
fun hello(@PathParam("name") name:String): String {
return "hello ${name}"
}
}
运⾏命令启动服务
gradle quarkusDev
访问服务
curl localhost:8080/hello/jarrysix
> hello jarrysix
三、使⽤数据源
通过上⾯的步骤, 我们已能运⾏quarkus, 接下来我们通过极为简单的⽅式来完成数据源的访问.⾸先, 我们需要添加配置:
quarkus.datasource.db-kind=h2
quarkus.datasource.username=username-default
quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:default
quarkus.datasource.jdbc.min-size=3
quarkus.datasource.jdbc.max-size=13
创建实体类
@Entity
public class Gift {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="giftSeq")
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private String name;
public String getName() {
return name;
}
}
创建Panachec仓储类
@ApplicationScoped
public class PersonRepository implements PanacheRepository<Person> {
// put your custom logic here as instance methods public void deletePerson(name:String){
delete("name",name);
}
}
在资源类中调⽤仓储类
@Path("/person")
class HelloResource {
@Inject
private lateinit var repo:PersonRepository
@DELETE@Path("/{name}")
fun delete(@PathParam("name") name:String): String {
return "success"
}
}
当然在实际项⽬中不建议直接调⽤仓储,  就这样我们完成⼈员删除的服务.
三:使⽤docker打包镜像
quarkus可以通过GraalVM打包成原⽣镜像, 以在⽣产环境中得到更低的CPU和内存占⽤.  如果您不想本地打包, 可以使⽤docker镜像打包为原⽣应⽤.
本⽂为了简化, 依然使⽤JVM来运⾏quarkus, 镜像构建配置⽂件如下:
# Quarkus docker image demo
# Version 1.0
# Author : jarrysix(homepage: fze)
# Date : 2018-04-13 14:40
FROM adoptopenjdk/openjdk14-openj9:alpine-jre
MAINTAINER jarrysix
WORKDIR /data
WORKDIR /app
COPY build/*.jar ./
COPY build/lib ./lib
RUN sed -i 's//mirrors.ustc.edu/g' /etc/apk/repositories && \
apk add tzdata fontconfig ttf-dejavu && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
EXPOSE 8080
ENTRYPOINT ["java","-jar *-runner.jar"]
四:使⽤代码⽣成器
因为quarkus的项⽬结构及对框架和⼯具依赖较低,  甚⾄仔细观察,  项⽬代码⾥⼤多引⽤的就是JAVA⾃带的⼯具集.  这样对我们使⽤代码⽣成器来⽣成⼀些格式重复的代码是相当有利的.
我在⽣产环境中, 就⽤⽣成器来⽣成quarkus和vue.js的代码. 极⼤的减少了⼯作量.  接下来我们⼀步⼀步的创建代码模板并⽣成代码.
注:⽂中使⽤的是go编写的代码⽣成器:tto , 项⽬主页: github/ixre/tto ; 其他⼯具也可以达到效果
1. 数据实体代码模板:  pojo.java
#!target:java/{{.global.Pkg}}/pojo/{{.table.Title}}Entity.java
package {{pkg "java" .global.Pkg}}.pojo;
import javax.persistence.Basic;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.GenerationType;
import javax.persistence.GeneratedValue;
/** {{.table.Comment}} */
@Entity
@Table(name = "{{.table.Name}}", schema = "{{.table.Schema}}")
public class {{.table.Title}}Entity {
{{range $i,$c := .columns}}{{$type := type "java" $c.Type}}
{{if $c.IsPk}}\
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY){{else}}
@Basic{{end}}
@Column(name = "{{$c.Name}}"{{if not $c.NotNull}}, nullable = true{{end}} {{if ne $c.Length 0}},length = {{$c.Length}}{{end}})
private {{$type}} {{$c.Name}};
/** {{$c.Comment}} */
public {{$type}} get{{$c.Prop}}() {
return this.{{$c.Name}};
}
public void set{{$c.Prop}}({{$type}} {{$c.Name}}){
this.{{$c.Name}} = {{$c.Name}};
}
{{end}}
/** 拷贝数据  */
public {{.table.Title}}Entity copy({{.table.Title}}Entity src){
{{.table.Title}}Entity dst = new {{.table.Title}}Entity();
{{range $i,$c := .columns}}
dst.set{{$c.Prop}}({{$c.Prop}}());{{end}}
return dst;
}
}
2. 仓储代码模板:  quarkus_repo.kt
#!target:kotlin/{{.global.Pkg}}/repo/{{.table.Title}}
package {{pkg "java" .global.Pkg}}.repo;
import {{pkg "kotlin" .global.Pkg}}.pojo.{{.table.Title}}Entity
import io.panache.PanacheRepository
t.ApplicationScoped
{{$pkType := type "kotlin" .table.PkType}}
/** {{.table.Comment}}仓储 */
@ApplicationScoped
class {{.table.Title}}JpaRepository : PanacheRepository<{{.table.Title}}Entity> {
}
3. 服务代码模板:quarkus_service.kt
#!target:kotlin/{{.global.Pkg}}/service/{{.table.Title}}
package {{pkg "java" .global.Pkg}}.service
import {{pkg "java" .global.Pkg}}.pojo.{{.table.Title}}Entity
import {{pkg "java" .global.Pkg}}.repo.{{.table.Title}}JpaRepository
import javax.inject.Inject
prise.inject.Default
t.ApplicationScoped
import net.fze.util.catch
import net.fzemons.std.Types
import net.fzemons.std.TypesConv
import net.fze.util.value
ansaction.Transactional
{{$tableTitle := .table.Title}}
{{$pkName := .table.Pk}}
{{$pkProp := lower_title .table.PkProp}}
{{$pkType := type "kotlin" .table.PkType}}
/** {{.table.Comment}}服务  */
@ApplicationScoped
class {{.table.Title}}Service {
@Inject@field:Default
private lateinit var repo: {{$tableTitle}}JpaRepository
fun parseId(id:Any):Long{Long(id)}
/** 根据ID查{{.table.Comment}} */
fun findByIdOrNull(id:{{$pkType}}):{{$tableTitle}}Entity?{
po.findByIdOptional(this.parseId(id))
}
/** 保存{{.table.Comment}} */
@Transactional
fun save{{$tableTitle}}(e: {{$tableTitle}}Entity):Error? {
return catch {
var dst: {{$tableTitle}}Entity
if (e.{{$pkProp}} > 0) {
dst = po.findById(this.parseId(e.{{$pkProp}}))!!
} else {
dst = {{$tableTitle}}Entity()
{{$c := try_get .columns "create_time"}}\
{{if ne $c nil}}ateTime = Types.time.unix().toLong(){{end}}
}
{{range $i,$c := exclude .columns $pkName "create_time" "update_time"}}            dst.{{lower_title $c.Prop}} = e.{{lower_title $c.Prop}}{{end}}\
{{$c := try_get .columns "update_time"}}
{{if ne $c nil}}dst.updateTime = Types.time.unix().toLong(){{end}} po.persistAndFlush(dst)
null
}.error()
}
/** 批量保存{{.table.Comment}} */
@Transactional
fun saveAll{{$tableTitle}}(entities:Iterable<{{$tableTitle}}Entity>){ po.persist(entities)
}
/** 删除{{.table.Comment}} */
@Transactional
fun deleteById(id:{{$pkType}}):Error? {
return catch {
}.error()
}
}
4. 资源类代码模板:restful_resource.kt
#!target:kotlin/{{.global.Pkg}}/resources/{{.table.Title}} package {{pkg "java" .global.Pkg}}.resources
import {{pkg "java" .global.Pkg}}.pojo.{{.table.Title}}Entity
import {{pkg "java" .global.Pkg}}.service.{{.table.Title}}Service
import {{pkg "java" .global.Pkg}}ponent.TinyQueryComponent
import net.fzemons.std.Result
import port.DataResult
import javax.inject.Inject
import javax.ws.rs.*
import javax.MediaType
t.RequestScoped
import javax.annotation.security.PermitAll
{{$tableTitle := .table.Title}}
{{$pkType := type "kotlin" .table.PkType}}
/* {{.table.Comment}}资源 */
@Path("/{{.table.Name}}")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequestScoped
class {{.table.Title}}Resource {
@Inject private lateinit var service:{{.table.Title}}Service
@Inject private lateinit var queryComponent: TinyQueryComponent
/** 获取{{.table.Comment}} */
@GET@Path("/{id}")
@PermitAll
fun get(@PathParam("id") id:{{$pkType}}): {{.table.Title}}Entity? { return service.findByIdOrNull(id)
}
/** 创建{{.table.Comment}} */
@POST
@PermitAll
fun create(entity: {{.table.Title}}Entity):Result {
val err = this.service.save{{.table.Title}}(entity)
if(err != null)ate(ssage)
return Result.OK
}
/** 更新{{.table.Comment}} */
@PUT@Path("/{id}")
@PermitAll
fun save(@PathParam("id") id:{{$pkType}},entity: {{.table.Title}}Entity):Result {        entity.{{lower_title .table.PkProp}} = id
val err = this.service.save{{.table.Title}}(entity)
if(err != null)ate(ssage)
return Result.OK
}
/** 删除{{.table.Comment}} */
@DELETE@Path("/{id}")
@PermitAll
fun delete(@PathParam("id") id:{{$pkType}}):Result {
val err = this.service.deleteById(id)
if(err != null)ate(ssage)
return Result.OK
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。