라인 엔지니어가 올려놓은 샘플 응용해서 Armeria, JPA 이용한 간단한 gRPC API 만들어본거 정리(아직 webflux + R2DBC 를 시도해보진 못했다)
Armeria
- https://armeria.dev/
- https://github.com/line/armeria : 사용자 친화적인 Java microservice framework.
- asynchronous, reactive model 기반.
- Netty, gRPC, Thrift에 기반한 오픈소스 비동기 HTTP/2 RPC/REST 클라이언트/서버 라이브러리.
gRPC 서버
- 높은 생산성과 효율적인 유지보수
- 다양한 언어와 플랫폼 지원 : C/C++, go, java, C#, python, node.js 등
- HTTP/2 기반의 양방향 스트리밍
- 높은 메시지 압축률과 성능
개발
설정
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'com.google.protobuf' version '0.8.12'
}
group = 'com.test.dc_api'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.linecorp.armeria:armeria-spring-boot2-webflux-starter'
implementation 'com.linecorp.armeria:armeria-grpc'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.1'
}
test {
useJUnitPlatform()
}
dependencyManagement {
imports {
mavenBom 'com.linecorp.armeria:armeria-bom:0.99.9'
mavenBom 'io.netty:netty-bom:4.1.51.Final'
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:4.0.0-rc-2"
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java:1.31.0'
}
}
generateProtoTasks {
all()\*.plugins {
grpc {}
}
all().each { task ->
task.generateDescriptorSet = true
task.descriptorSetOptions.includeSourceInfo = true
task.descriptorSetOptions.includeImports = true
task.descriptorSetOptions.path =
"${buildDir}/resources/main/META-INF/armeria/grpc/service-name.dsc"
}
}
}
sourceSets {
main {
java {
srcDir 'build/generated/source/proto/main/grpc'
srcDir 'build/generated/source/proto/main/java'
}
}
}
서버 개발
proto
syntax = "proto3";
package com.test.dc_api.proto;
option java_package = "com.test.dc_api.proto";
option java_outer_classname = "TaskOrdGrpcProto";
import "google/protobuf/timestamp.proto";
service TaskOrd {
rpc GetTaskOrd (TaskOrdReq) returns (stream TaskOrdRes) {}
}
message TaskOrdReq {
string taskOrdId = 1;
}
message TaskOrdRes {
string taskOrdId = 1;
string taskActCd = 2;
string taskTpCd = 3;
google.protobuf.Timestamp taskStDt = 4;
google.protobuf.Timestamp taskCmpltDt = 5;
string twinTandCd = 6;
}
controller
@Controller
@AllArgsConstructor
public class DcApiController {
private final DcApiService dcApiService;
@GetMapping("/task_ord")
@ResponseBody
public List getTaskOrd(String taskOrdId) {
return dcApiService.getTaskOrd(taskOrdId);
}
}
service
grpc service
@Service
@AllArgsConstructor
public class TaskOrdGrpcService extends TaskOrdGrpc.TaskOrdImplBase {
private final DcApiService dcApiService;
@Override
public void getJobOrd(
TaskOrdGrpcProto.TaskOrdReq request,
StreamObserver<TaskOrdGrpcProto.TaskOrdRes> responseObserver) {
String taskOrdId = request.getTaskOrdId();
dcApiService.getTaskOrd(taskOrdId)
.forEach(e -> {
responseObserver.onNext(e.toProto());
});
}
}
service
@Service
@RequiredArgsConstructor
public class DcApiService {
@Autowired
TaskOrdRepository taskOrdRepository;
public List getTaskOrd(String taskOrdId) {
return taskOrdRepository.findByTaskOrdId(taskOrdId);
}
}
entity
@Entity
@Data
@Table(name = "task_ord")
@JsonIgnoreProperties({ "id" })
public class TaskOrd implements Persistable, Serializable {
@Id
@Column(name = "regSeq")
@GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmmss")
private LocalDateTime evntDt;
private String taskOrdId;
private String taskActCd;
private String taskTpCd;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmmss")
private LocalDateTime taskStDt;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMddHHmmss")
private LocalDateTime taskCmpltDt;
public TaskOrdGrpcProto.TaskOrdRes toProto() {
return TaskOrdGrpcProto.TaskOrdRes.newBuilder()
.setTaskOrdId(getTaskOrdId())
.setTaskActCd(getTaskActCd())
.setTaskTpCd(getTaskTpCd())
...
.build();
}
}
config
@Configuration
@RequiredArgsConstructor
public class ArmeriaServerConfiguration {
private final DemoGrpcService demoGrpcService;
//private final AlertHstGrpcService alertHstGrpcService;
private final TaskOrdGrpcService taskOrdGrpcService;
@Bean
public ArmeriaServerConfigurator armeriaServerConfigurator() {
return serverBuilder -> {
serverBuilder.decorator(LoggingService.newDecorator());
serverBuilder.decorator(ContentPreviewingService.newDecorator(Integer.MAX_VALUE, StandardCharsets.UTF_8));
serverBuilder.accessLogWriter(AccessLogWriter.combined(), false);
serverBuilder.service(GrpcService.builder()
.addService(demoGrpcService)
//.addService(alertHstGrpcService)
.addService(TaskOrdGrpcService)
.supportedSerializationFormats(GrpcSerializationFormats.values())
.enableUnframedRequests(true)
.build());
serverBuilder.serviceUnder("/docs", new DocService());
};
}
}
- test
% curl --http2 -v http://localhost:9991/task_ord?taskOrdId='5288717'
- generate pb
% python -m grpc_tools.protoc -I./protos --python_out=.. --grpc_python_out=.. ./protos/task_ord.proto
다음 두 파일 생성됨 : task_ord_pb2.py, task_ord_pb2_grpc.py
- python client
from future import print_function
import logging
import grpc
import task_ord_pb2
import task_ord_pb2_grpc
def run():
with grpc.insecure_channel('localhost:9991') as channel:
stub = task_ord_pb2_grpc.TaskOrdStub(channel)
response = stub.GetTaskOrd(task_ord_pb2.TaskOrdReq(taskOrdId='5288717'))
# print(response)
try:
i = 1
for res in response:
print(i, res)
i += 1
except grpc._channel._Rendezvous as err:
status_code = err.code()
if grpc.StatusCode.CANCELLED == status_code:
print("end")
pass
else:
print(err)
if name == 'main':
logging.basicConfig()
run()
'Lang' 카테고리의 다른 글
MS Azure Blob Storage 에 json 데이터 업로드 (0) | 2020.09.11 |
---|---|
[python]pandas, mongoDB, odo 간단한 테스트 (2) | 2020.08.21 |
[python]cx_oracle 설정, 사용 예 (0) | 2020.07.28 |
[Python]두 DB 간 테이블 동기화 코딩 (0) | 2020.07.21 |
[Java]여러 DB 환경에서 native query 쓸 때 orm.xml 문제 (0) | 2020.07.15 |