云原生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的项目.  项目的配置文件: build.gradle.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("javax.enterprise.context.ApplicationScoped")
    annotation("javax.enterprise.context.RequestScoped")
}

repositories {
    maven("http://maven.aliyun.com/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")
    testImplementation("io.quarkus:quarkus-junit5:1.4.2.Final")
}
tasks.withType<Test> {
    useJUnitPlatform()
}
// 代码生成器
tasks.create("generate").doFirst {
    exec{
        workingDir("./tto")
        commandLine("sh","-c","./tto.sh")
    }
}
tasks.withType<JavaCompile>().configureEach {
    options.encoding="utf-8"
    options.compilerArgs = 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.ws.rs.core.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 http://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 methodspublic 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 {
        this.repo.deletePerson(name);
        return "success"
    }
}

复制代码

当然在实际项目中不建议直接调用仓储,  就这样我们完成人员删除的服务.

 

三:使用docker打包镜像

quarkus可以通过GraalVM打包成原生镜像, 以在生产环境中得到更低的CPU和内存占用.   如果您不想本地打包, 可以使用docker镜像打包为原生应用.

本文为了简化, 依然使用JVM来运行quarkus, 镜像构建配置文件如下:

复制代码

# Quarkus docker image demo
# Version 1.0
# Author : jarrysix(homepage: http://fze.net)
# 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/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/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 , 项目主页: http://github.com/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}}(src.get{{$c.Prop}}());{{end}}
        return dst;
    }
}

复制代码

2. 仓储代码模板:  quarkus_repo.kt

复制代码

#!target:kotlin/{{.global.Pkg}}/repo/{{.table.Title}}JpaRepository.kt.gen
package {{pkg "java" .global.Pkg}}.repo;

import {{pkg "kotlin" .global.Pkg}}.pojo.{{.table.Title}}Entity
import io.quarkus.hibernate.orm.panache.PanacheRepository
import javax.enterprise.context.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}}Service.kt.gen
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
import javax.enterprise.inject.Default
import javax.enterprise.context.ApplicationScoped
import net.fze.util.catch
import net.fze.commons.std.Types
import net.fze.commons.std.TypesConv
import net.fze.util.value
import javax.transaction.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{return TypesConv.toLong(id)}

    /** 根据ID查找{{.table.Comment}} */
    fun findByIdOrNull(id:{{$pkType}}):{{$tableTitle}}Entity?{
        return this.repo.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 = this.repo.findById(this.parseId(e.{{$pkProp}}))!!
            } else {
                dst = {{$tableTitle}}Entity()
                {{$c := try_get .columns "create_time"}}\
                {{if ne $c nil}}dst.createTime = 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}}
            this.repo.persistAndFlush(dst)
            null
        }.error()
    }

    /** 批量保存{{.table.Comment}} */
    @Transactional
    fun saveAll{{$tableTitle}}(entities:Iterable<{{$tableTitle}}Entity>){
        this.repo.persist(entities)
        this.repo.flush()
    }

    /** 删除{{.table.Comment}} */
    @Transactional
    fun deleteById(id:{{$pkType}}):Error? {
        return catch {
            this.repo.deleteById(this.parseId(id))
        }.error()
    }

}

复制代码

4. 资源类代码模板:restful_resource.kt

复制代码

#!target:kotlin/{{.global.Pkg}}/resources/{{.table.Title}}Resource.kt.gen
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}}.component.TinyQueryComponent
import net.fze.commons.std.Result
import net.fze.component.report.DataResult
import javax.inject.Inject
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.enterprise.context.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)return Result.create(1,err.message)
        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)return Result.create(1,err.message)
        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)return Result.create(1,err.message)
        return Result.OK
    }

    /** {{.table.Comment}}列表 */
    @GET
    @PermitAll
    fun list(): List<{{.table.Title}}Entity> {
        return mutableListOf()
    }

    /** {{.table.Comment}}分页数据 */
    @GET@Path("/paging")
    @PermitAll
    fun paging(@QueryParam("params") params:String,
               @QueryParam("page") page:String,
               @QueryParam("rows") rows:String
    ): DataResult {
        return this.queryComponent.fetchData("default",
                "{{.table.Title}}List", params, page, rows)
    }
}

复制代码

5. VUE接口文件代码模板:api.ts

复制代码

#!lang:ts#!name:API和定义文件
#!target:ts/feature/{{.table.Prefix}}/{{.table.Name}}/api.ts
import request from '@/utils/request'


// {{.table.Comment}}对象
export interface I{{.table.Title}} {
    {{range $i,$c := .columns}}// {{$c.Comment}}
    {{lower_title $c.Prop}}:{{type "ts" $c.Type}}
    {{end}}
}

export const default{{.table.Title}}:()=>I{{.table.Title}}=()=>{
    return {
        {{range $i,$c := .columns}}
        {{lower_title $c.Prop}}:{{default "ts" $c.Type}},{{end}}
    };
}

export const get{{.table.Title}} = (id: any, params: any = {}) =>
    request({
        url: `/{{.table.Name}}/${id}`,
        method: 'get',
        params:{...params}
    })

export const get{{.table.Title}}List = (params: any = {}) =>
    request({
        url: '/{{.table.Name}}',
        method: 'get',
        params:{...params}
    })

export const create{{.table.Title}} = (data: any) =>
    request({
        url: '/{{.table.Name}}',
        method: 'post',
        data
    })

export const update{{.table.Title}} = (id: any, data: any) =>
    request({
        url: `/{{.table.Name}}/${id}`,
        method: 'put',
        data
    })

export const delete{{.table.Title}} = (id: any) =>
    request({
        url: `/{{.table.Name}}/${id}`,
        method: 'delete'
    });

export const batchDelete{{.table.Title}} = (arr: any[]) =>
    request({
        url: '/{{.table.Name}}',
        method: 'delete',
        data:arr
    });


export const getPaging{{.table.Title}} = (page:number,rows:number,params: any) =>
    request({
        url: '/{{.table.Name}}/paging',
        method: 'get',
        params:{page,rows,params}
    })

复制代码

运行命令将代码生成到指定位置

gradle generate

 

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页