REST(RE Presentational State Transfer)는
Roy Fielding이 2000년에 발표한 논문에 처음 등장한 분산형 하이퍼미디어
시스템의 아키텍처 스타일입니다.
CHAPTER 5 : Representational State Transfer (REST)
This chapter introduces and elaborates the Representational State Transfer (REST) architectural style for distributed hypermedia systems, describing the software engineering principles guiding REST and the interaction constraints chosen to retain those principles, while contrasting them to the constraints of other architectural styles. REST is a hybrid style derived from several of the network-based architectural styles described in Chapter 3 and combined with additional constraints that define a uniform connector interface. The software architecture framework of Chapter 1 is used to define the architectural elements of REST and examine sample process, connector, and data views of prototypical architectures.
REST는 몇가지 제약 조건이 있으며, 이 가이드를 준수한 인터페이스를 RESTful하다고 표현합니다.
하이브리드 스타일에 추가된 첫 번째 제약 조건은 Client-Server의 관심사를 분리시키는 것입니다.
사용자 인터페이스 문제와 데이터 저장 등의 문제를 분리함으로써 여러 플랫폼에서 사용자 인터페이스의 이식성을 높이고,
서버 구성 요소를 단순화하여 확장성을 향상시킬 수 있습니다.
하이브리드 스타일은 HTTP에서 영감을 얻었기 때문에, HTTP의 기본 원칙인 Stateless가 반영되었습니다.
서버는 서비스 요청에 대한 어떤 것도 저장하지 않으며, 클라이언트의 모든 요청에는 해당 요청을 이 해할 수 있는 모든 정보가 포함되어야 합니다.
요청에 대한 응답 내의 데이터에 암시적 또는 명시적으로 캐시 가능/불가능 라벨이 지정 되어야 합니다.
이러한 캐시 사용은 일부 상호 작용을 부분적 또는 완전히 제거하여 평균 대기 시간을 줄여 효율성, 확장성 및 사용자의 인식 성능을 향상 시킬 수 있습니다.
그러나 캐시 내의 오래된 데이터와 서버에 존재하는 데이터가 다를 경우 안정성이 감소 할 수 있습니다.
REST 아키텍처 스타일의 핵심은 구성 요소 간의 균일한 인터페이스를 강조하는 것입니다.
소프트웨어 엔지니어링의 일반 원칙을 컴포넌트 인터페이스에 적용함으로써 전체 시스템 아키텍처가 단순화되고 가시성이 향상됩니다.
이러한 아키텍처는 서비스와 분리되어 독립적으로 발전 할 수 있으나, 표준화된 형식이 아닌 경우 균일한 인퍼페이스는 오히려 효율성을 저하 시킬 수 있습니다.
즉, REST 인터페이스는 대규모 하이퍼미디어 데이터 전송에 효율적으로 설계되어 웹의 일반적인 경우에는 최적의 효율을 낼 수 있지만, 다른 형태의 아키텍처 상호 작용에는 적합하지 않을 수 있습니다.
인터넷 규모 요구 사항에 대한 동작을 개선하기 위해 추가 된 제약 조건입니다.
계층형 시스템 스타일은 각 구성 요소가 상호 작용하는 인접 계층을 볼 수 없도록 구성 요소 동작을 제한합니다.
이러한 구조는 각 계층의 독립성을 보장하며 보안, 로드 밸런싱, 암호화 등 여러 계층을 유연성 있게 추가/삭제 할 수 있습니다.
애플릿이나 스크립트 형식으로 코드를 다운로드하고 이를 실행 할 수 있는 기능을 제공합니다.
이 기능은 시스템에 유연성과 확장성을 제공 할 수 있지만, 가시성을 감소시키므로 선택적인 제약 사항입니다.
REST 스타일을 제시한 시점에는 Web은 대부분 정적 document이었으며, 클라이언트에서 비즈니스 로직을 구현하기에는 많은 어려움이 있었습니다.
이러한 환경을 고려하여 Code-On-Demand 기능을 선택적으로나마 제약 조건으로 추가한 것으로 추측 됩니다.
현재에는 보안상 문제로 인해 거의 적용하지 않습니다.
Restfull한 API를 설계하기 위해 다음의 4가지 원칙을 준수합니다.
가장 기본적인 원칙으로 URL만으로 어떤 자원을 제어 할 수 있는지 알 수 있어야 합니다.
즉, URL은 특정 자원의 위치 및 종류에 대한 정보를 포함하며 URL만으로 자원을 명확히 식별 할 수 있어야 합니다.
자원을 제어하기 위한 행위는 명시적이어야 합니다.
강제된 사항은 아니지만, 일반적으로 HTTP Method으로 표현합니다.
데이터 처리를 위한 모든 정보가 포함 되어야 합니다.
Hypermedia as the Engine of Application Sate.
서버는 하이퍼미디어를 이용하여 애플리케이션 상태 전이를 수행 할 수 있어야 합니다.
Restfull API를 설계하기 위해 다음의 규칙들을 권장합니다.
[참고 문서]
Gitlab runner에서 job 실행 시, 설정한 계정이 아닌 root로 실행 되는 경우 발생
gitlab-runner를 특정 계정으로 실행하기 위해 service에 등록합니다.
(gitlab-runner install은 gitlab-runner을 설치하는 것이 아닌, 서비스에 등록하기 위한 명령어입니다.)
# 계정 생성
$ sudo useradd --create-home uatm --shell /bin/bash
# 서비스 생성
$ sudo gitlab-runner install --user=uatm --working-directory=/home/uatm
# 서비스 실행
$ sudo gitlab-runner start
gitlab-runner run은 job을 실행키기 위한 명령어입니다.
명령은 실행되어 신호를 받을 때까지 대기합니다.
$ sudo gitlab-runner run
Gradle을 이용하여 test와 build를 수행하는 job을 생성합니다.
stages:
- test
- build
- deploy
cache:
paths:
- build/
test:
stage: test
script:
- echo 'Testing...'
- chmod +x gradlew
- ./gradlew test
tags:
- build
build:
stage: build
script:
- echo 'Building...'
- chmod +x gradlew
- ./gradlew clean
- ./gradlew assemble
tags:
- build
일부 디렉토리(build, .gradle) 및 파일(gradlew)의 소유자가 root로 변경되어 있습니다.
2번째로 실행 된, build job에서 실행한 script 명령어가 root의 권한으로 수행된 것입니다.
gitlab-runner의 start와 run 명령을 모두 실행 시켜 발생한 현상입니다.
서비스에 등록된 gitlab-runner
가 job 실행시키면 설정된 계정으로 실행되지만,
gitlab-runner의 run 명령어로 생성된 프로세스
가 job을 실행하게 되면 root 권한으로 실행 된 것입니다.
gitlab-runner의 명령어(run / start / install)를 제대로 이해하지 못하고 사용하다보니 문제 해결에 오랜 시간이 걸렸습니다.
공식 메뉴얼을 좀 더 상세히 읽어야하겠습니다.
Querydsl은 JPA, MongoDB 및 Java SQL을 포함한 여러 백엔드에 대해 유형이 안전한 SQL 유사 쿼리를 생성할 수 있는 프레임워크입니다.
쿼리를 인라인 문자열로 작성하거나 XML 파일로 외부화하는 대신 유창한 API를 통해 구성됩니다.
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
dependencies {
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
}
Gradle plugin(com.ewerk.gradle.plugins.querydsl)을 사용하는 방법도 있으나, 해당 플러그인의 버전과 Intellij의 버전 등의 호환성으로 문제가 발생할 수도 있습니다.
참조 : [gradle] 그레이들 Annotation processor 와 Querydsl
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
QClass 생성 경로 등을 설정합니다.
gradle cleanQuerydslSourcesDir
gradle compileQuerydsl
대부분 버전간 호환성이나 Gradle / IDE 설정이 문제인 경우가 많습니다.
이런 경우 아래 사항들을 확인해보시기 바랍니다.
test.java : error: cannot find symbol
import test.QTest;
^
QClass가 생성되어야 위 코드가 정상적으로 수행될텐데, 위 QTest라는 클래스가 없다며 빌드가 안되는 현상이 발생했습니다.
이러한 현상은 Class 또는 Interfaced를 정의 할 때, 상속 받는 Class나 추상 클래스의 구조가 다른 경우입니다.
( error: interface expected / Wrong number of type arguments)
Airflow™ is a platform created by the community to programmatically author, schedule and monitor workflows.
데이터 파이프라인을 위한 오픈 소스 워크플로우 관리 플랫폼입니다.
Python으로 작성된 DAG를 워크플로우 형태로 실행 할 수 있습니다.
pip으로 간단하게 설치하고, 관리를 위한 WEB을 제공합니다.
[설치 방법]
Airflow의 Connection는 외부 서비스에 연결하는데 필요한 자격 증명 및 기타 정보를 저장하는데 사용됩니다.
연결 정보는 아래의 방법으로 정의 할 수 있습니다.
JSON 또는 URI 형식의 환경 변수로 정의합니다. [상세]
JSON 형식
export AIRFLOW_CONN_MY_PROD_DATABASE='{
"conn_type": "my-conn-type",
"login": "my-login",
"password": "my-password",
"host": "my-host",
"port": 1234,
"schema": "my-schema",
"extra": {
"param1": "val1",
"param2": "val2"
}
}'
URI 형식
export AIRFLOW_CONN_MY_PROD_DATABASE='my-conn-type://login:password@host:port/schema?param1=val1¶m2=val2'
Airflow와 연동되는 서비스의 공급자를 통해 연결 정보를 정의합니다. [상세]
지원하는 제공자는 다음과 같습니다.
Amazon
- SecretsManagerBackend
- SystemsManagerParameterStoreBackend
- CloudSecretManagerBackend
Hashicorp
- VaultBackend
Microsoft Azure
- AzureKeyVaultBackend
데이터베이스에 연걸 정보를 저장합니다.
Connection Type missing? Make sure you’ve installed the corresponding Airflow Provider Package.
필요 시 Airflow Provider를 추가합니다.
Providers packages reference
CLI에서 JSON형식으로 연결을 추가 할 수 있습니다.
airflow connections add 'my_prod_db' \
--conn-json '{
"conn_type": "my-conn-type",
"login": "my-login",
"password": "my-password",
"host": "my-host",
"port": 1234,
"schema": "my-schema",
"extra": {
"param1": "val1",
"param2": "val2"
}
}'
Spring Boot JPA로 Entity 설계 시, 컬럼에 Null을 허용하지 않도록 설정하기 위해
@NotNull 또는 @Column(nullable = false) 을 사용합니다.
이 둘의 차이점에 대해 간략하게 정의하고자 합니다.
Spring JPA에서 제공되는 이노테이션으로 Entity의 컬럼을 지정합니다.
이 때, 아래의 속성들을 지정할 수 있습니다.
JSR-303/JSR-349 Bean Validation
Spring Framework 4.0 supports Bean Validation 1.0 (JSR-303) and Bean Validation 1.1 (JSR-349) in terms of setup support, also adapting it to Spring’s Validator interface.
An application can choose to enable Bean Validation once globally, as described in Section 7.8, “Spring Validation”, and use it exclusively for all validation needs.
An application can also register additional Spring Validator instances per DataBinder instance, as described in Section 7.8.3, “Configuring a DataBinder”. This may be useful for plugging in validation logic without the use of annotations.
Spring Framework 4.0부터 지원되는 Bean Valication입니다.
주로 Spring의 Validator가 유효성 검증을 수행할 때 사용되는 이노테이션입니다.
@Column(nullable = false)로 설정하는 것은 Entity 컬럼의 null 허용 여부를 설정하는 것이고,
@NotNull은 Java Bean의 null 유효성 검증을 위한 설정입니다.
즉, 위 2개의 이노테이션은 사용 목적이 다릅니다.
@Entity
public class Test {
@Id
private int id;
@Column(nullable = false)
private String name;
}
Entity와 Table의 DDL에 NOT NULL로 설정되고 DB에 Null 값을 입력 할 때 오류가 발생합니다.
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null];
@Entity
public class Test {
@Id
private int id;
@NotNull
private String name;
}
Entity의 유효성 검증 시 오류가 발생합니다.
ConstraintViolationImpl{interpolatedMessage=’널이어서는 안됩니다’, propertyPath=name, rootBeanClass=class
@Column(nullable = false)은 DB에 제약조건을 거는 것이고,
@NotNull은 유효성 검사를 수행하는 것입니다.
hibernate는 @NotNull가 설정되어 있으면 자동으로 DB에 제약조건을 추가해줍니다.
@NotNull = @Column(nullable = false) + 유효성 검사
Spring에서 LocalDateTime을 JSON으로 변환 시 Array 형태로 표출 되는 현상이 발생하였습니다.
{
"id": 1,
"name": "test",
"createdAt": [
2023,
7,
27,
15,
47,
7,
972737100
],
"updatedAt": [
2023,
7,
27,
15,
47,
7,
972737100
]
}
Spring 2.0.0이상의 버전에서는 데이터 반환 시 별다른 설정을 하지 않아도 LocatDateTime의 값을 String 으로 자동으로 변환하여 반환합니다.
이는 jackson-datatype-jsr310가 기본적으로 포함되어 있어 있기 때문입니다.
Add-on module to support JSR-310 (Java 8 Date & Time API) data types.
하지만, EnableWebMvc를 설정하면 Springd은 자체적으로 기본적인 ObjectMapper를 구성하게 됩니다.
이로 인해 직렬화와 관련된 설정들이 변경될 수 있으며, 사용자 정의 설정이 무시될 수 있습니다.
(Spring 2.7.0 기준)
Spring이 WebMvcConfigurerAdapter 클래스를 자동으로 등록할 때, configureMessageConverters 메서드를 재정의하고. 이 때 jackon-datatype-jar310이 적용되지 않는 것으로 추측됩니다.
이러한 문제를 해결하기 위해서는 해당 필드에 JsonFormat를 지정하거나, ObjectMapper를 등록 또는 재정의하는 방법이 있습니다.
@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy/MM/dd'T'HH:mm:ss")
private LocalDateTime createdAt;
간단하게 적용 할 수 있는 장점이 있지만, 매번 필드에 정의 해줘야 하는 불편함이 있습니다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(customObjectMapper());
converters.add(converter);
}
@Bean
public ObjectMapper customObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 여기에 원하는 ObjectMapper 설정을 추가할 수 있습니다.
return objectMapper;
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
}
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
return new MappingJackson2HttpMessageConverter(builder.build());
}
}