Hibernate6에서 Multitenancy 구현 방법

Multitenancy를 구현할때 다음 3가지 방법중 하나를 선택해야한다.

구분 database schema
shared schema O O
shared database O X
different database X NA

각각의 방법은 장단점이 있으며 여기 논하지 않는다.
shared schema는 tenant가 구분되어야 할 entity에 대한 독립성 유지을 유지해야 하며,
코드 수준에서 설계된 시스템은 정보유출 위험이 생길 수 밖에 없다.
그러므로, framework 수준에서 tenant를 독립성을 보장할 수 있는 방법으로 구현해야한다.

다음은 hibernate 6을 통해 쉽게 shared schema 를 구현하는 코드의 일부이다.

entity 정의

@TenantId를 사용하여 entity class에 테넌트로 구분할 필드를 정의한다.

@Entity
@Table
public class Owner {

    @Id
    @GeneratedValue
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    @TenantId
    @Column(name = "tenant")
    private String tenant;
}

Resolver component

다음은 @TenantId 로 정의된 entity에 대하여 CRUD가 발생되는 경우 tenant정보를 추출하는 component 이다

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver, HibernatePropertiesCustomizer {

    @Override
    public String resolveCurrentTenantIdentifier() {
        return .. // 세션,토큰,requset 등에서 tenant 정보를 추출;
    }

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, this);
    }
}

주의사항

  • 당연히 Native Query 에는 적용되지 않는다.
  • @OneToMany 관계인 경우 적용되지 않는다.

주요 생성형 AI 서비스와 구현방식

AI matrix head png sticker

주요 생성형 AI 서비스에는 다음과 같은 것들이 있습니다:

  1. 자연어 처리(Natural Language Processing, NLP) 기반의 생성형 AI : GPT-3, GPT-2, BERT, Transformer 등이 있습니다. 이들은 텍스트 생성, 번역, 요약, QA(Question Answering), 대화형 AI 챗봇 등의 다양한 응용분야에서 사용됩니다.
  2. 이미지 생성형 AI : DALL-E, GAN(Generative Adversarial Networks) 등이 있습니다. 이들은 기존 이미지를 학습하여 새로운 이미지를 생성하거나 이미지를 편집할 수 있습니다.
  3. 음성 생성형 AI : Tacotron, WaveNet, Deep Voice 등이 있습니다. 이들은 자연스러운 인간의 음성을 학습하고 재현할 수 있습니다.
  4. 그외

    비휴먼(bHuman) : 개인화된 비디오 대량 발송


브라우즈 AI(Browse AI) : 웹사이트 모니터링


클립드롭(ClipDrop) : 지능형 디자인 도우미


컴포즈 AI(Compose AI) : 이메일 응답


데스크립트 오버덥 : 음성 복제


D-ID.com : 실시간 비디오 제작


듀러블(Durable) : 30초만에 웹 사이트 구축


카이버(Kaiber) : 설명만 하면 만들어지는 애니메이션


노션 AI(Notion AI) : 지능형 협업


픽토리(Pictory) : 비디오 편집을 위한 만능 툴


Rewind.ai : 맥에서만 가능한 되감기 기능


런웨이(Runway) : 손쉬운 동영상 조작


신세시아(Synthesia) : 전문가를 위한 빠른 비디오 제작


토움(Tome) : 버튼 하나로 만드는 프리젠테이션

이러한 생성형 AI들은 대부분 딥러닝(Deep Learning) 알고리즘을 기반으로 합니다. 딥러닝은 인공신경망(Artificial Neural Network)을 이용하여 다양한 패턴을 학습하고, 이를 기반으로 새로운 데이터를 생성하거나 분류하는 등의 작업을 수행합니다.

이를 위해서는 먼저 데이터를 수집하고, 이를 전처리(preprocessing)하여 모델의 학습에 적합한 형태로 만들어야 합니다. 이후에는 모델의 구조와 하이퍼파라미터(Hyperparameter)를 설정하고, 학습과 검증을 반복하여 모델의 성능을 개선합니다.

모델의 구현방식은 프로그래밍 언어나 딥러닝 프레임워크에 따라 다를 수 있습니다. 대표적인 딥러닝 프레임워크로는 TensorFlow, PyTorch, Keras, MXNet 등이 있습니다. 이러한 프레임워크를 이용하여 생성형 AI 모델을 구현하고 학습시킬 수 있습니다.

기억력을 향상시키려면?

person holding a book
기억력을 향상시키는 방법에는 다음과 같은 것들이 있습니다:
  1. 새로운 정보를 중심으로 기억을 향상시키는 연습을 하세요.
  2. 다양한 정보를 기억하기 위해 매일 새로운 것들을 배우세요.
  3. 기억해야 할 정보를 직접 연습하고 실제로 적용하세요.
  4. 기억하기 쉬운 방식으로 정보를 정리하고 기억하세요.
  5. 매일 복습을 통해 기억한 정보를 지속적으로 새로고침하세요.
  6. 매일 충분한 수면, 적당한 운동, 건강한 식습관을 유지하세요.
  7. 집중력을 개선하기 위해 광고, 소음, 스트레스 등의 영향을 최소화하세요.

아는 것과 실행은 다른 세상 이야기
– feat by ChatGPT –

기본키(PK)로 UUID의 문제점과 그 대안

원본 출처

소개

이 기사에서는 기본 키 제약 조건이 있는 데이터베이스 열에 가장 적합한 UUID(Universally Unique Identifier) ​​유형을 살펴보겠습니다.

표준 128비트 임의 UUID가 매우 인기 있는 선택이지만 이것이 데이터베이스 기본 키 열에 적합하지 않다는 것을 알게 될 것입니다.

표준 UUID 및 데이터베이스 기본 키

UUID(Universally Unique Identifier)는 식별자의 고유성을 보장하는 단일 중앙 시스템 없이도 독립적으로 생성할 수 있는 128비트 의사 난수 시퀀스입니다.

RFC 4122 사양은 다양한 데이터베이스 기능 또는 프로그래밍 언어로 구현되는 5가지 표준화된 UUID 버전을 정의합니다 .

예를 들어 UUID()MySQL 함수는 버전 1 UUID 번호를 반환합니다.

그리고 Java UUID.randomUUID()함수는 버전 4 UUID 번호를 반환합니다.

많은 개발자에게 이러한 표준 UUID를 데이터베이스 식별자로 사용하는 것은 다음과 같은 이유로 매우 매력적입니다.

  • ID는 애플리케이션에서 생성할 수 있습니다. 따라서 중앙 조정이 필요하지 않습니다.
  • 식별자 충돌 가능성은 매우 낮습니다.
  • id 값은 무작위이므로 사용자가 다른 식별자 값을 추측하여 다른 사람의 데이터를 보는 데 사용할 수 없으므로 안전하게 UI로 보낼 수 있습니다.

그러나 임의의 UUID를 데이터베이스 테이블 기본 키로 사용하는 것은 여러 가지 이유로 좋지 않습니다.

첫째, UUID는 거대합니다. 모든 단일 레코드는 데이터베이스 식별자로 16바이트가 필요하며 이는 연결된 모든 외래 키 열에도 영향을 미칩니다.

둘째, 기본 키 열에는 일반적으로 조회 또는 조인 속도를 높이기 위해 연결된 B+Tree 인덱스가 있으며 B+Tree 인덱스는 데이터를 정렬된 순서로 저장합니다.

그러나 B+Tree를 사용하여 임의의 값을 인덱싱하면 많은 문제가 발생합니다.

  • 값이 무작위로 제공되기 때문에 인덱스 페이지의 채우기 비율은 매우 낮습니다. 따라서 8kB의 페이지는 몇 가지 요소만 저장하게 되므로 인덱스 페이지가 버퍼 풀에 캐시될 수 있으므로 디스크와 데이터베이스 메모리 모두에서 많은 공간을 낭비하게 됩니다.
  • B+Tree 인덱스는 등거리 트리 구조를 유지하기 위해 자체적으로 균형을 재조정해야 하기 때문에 트리 구조를 채우는 미리 결정된 순서가 없기 때문에 무작위 키 값으로 인해 더 많은 인덱스 페이지 분할 및 병합이 발생합니다.

SQL Server 또는 MySQL을 사용하는 경우 전체 테이블이 기본적으로 클러스터된 인덱스 이기 때문에 상황은 더욱 악화됩니다 .

클러스터형 인덱스 테이블

그리고 이러한 모든 문제는 보조 인덱스 리프 노드에 기본 키 값을 저장하기 때문에 보조 인덱스에도 영향을 미칩니다.

클러스터형 인덱스 및 보조 인덱스

실제로 거의 모든 데이터베이스 전문가는 표준 UUID를 데이터베이스 테이블 기본 키로 사용하지 말라고 말합니다.

TSID – 시간순으로 정렬된 고유 식별자

기본 키 열에 UUID 값을 저장하려는 경우 TSID(시간 정렬 고유 식별자)를 사용하는 것이 좋습니다.

이러한 구현 중 하나 는 다음 두 부분으로 구성된 64비트 TSID를 제공하는 TSID Creator OSS 라이브러리 에서 제공됩니다 .

  • 42비트 시간 구성요소
  • 22비트 랜덤 구성 요소

랜덤 구성 요소는 두 부분으로 구성됩니다.

  • 노드 식별자(0~20비트)
  • 카운터(2~22비트)

tsidcreator.node애플리케이션을 부트스트랩할 때 시스템 속성 에서 노드 식별자를 제공할 수 있습니다 .


-Dtsidcreator.node="12"

노드 식별자는 TSIDCREATOR_NODE환경 변수를 통해 제공될 수도 있습니다.

export TSIDCREATOR_NODE="12"

라이브러리는 Maven Central에서 사용할 수 있으므로 다음 종속성을 통해 가져올 수 있습니다.

<dependency>
    <groupId>com.github.f4b6a3</groupId>
    <artifactId>tsid-creator</artifactId>
    <version>${tsid-creator.version}</version>
</dependency>

Tsid다음과 같이 최대 256개의 노드를 사용할 수 있는 개체를 만들 수 있습니다 .

Tsid tsid = TsidCreator.getTsid256();

개체 에서 Tsid다음 값을 추출할 수 있습니다.

  • 64비트 숫자 값,
  • 64비트 값을 인코딩하는 Crockford 의 Base32 문자열 값,
  • 42– 비트 시퀀스 에 저장된 에포크 이후 Unix 밀리초

이러한 값을 시각화하기 위해 로그에 인쇄할 수 있습니다.

long tsidLong = tsid.toLong();
String tsidString = tsid.toString();
long tsidMillis = tsid.getUnixMilliseconds();

LOGGER.info(
    "TSID numerical value: {}",
    tsidLong
);

LOGGER.info(
    "TSID string value: {}",
    tsidString
);

LOGGER.info(
    "TSID time millis since epoch value: {}",
    tsidMillis
);

그리고 다음과 같은 결과를 얻습니다.

TSID numerical value: 388400145978465528
TSID string value: 0ARYZVZXW377R
TSID time millis since epoch value: 1670438610927

10개의 값을 생성할 때:

for (int i = 0; i < 10; i++) {
    LOGGER.info(
        "TSID numerical value: {}",
        TsidCreator.getTsid256().toLong()
    );
}

값이 단조롭게 증가하는 것을 볼 수 있습니다.

TSID numerical value: 388401207189971936
TSID numerical value: 388401207189971937
TSID numerical value: 388401207194165637
TSID numerical value: 388401207194165638
TSID numerical value: 388401207194165639
TSID numerical value: 388401207194165640
TSID numerical value: 388401207194165641
TSID numerical value: 388401207194165642
TSID numerical value: 388401207194165643
TSID numerical value: 388401207194165644

굉장하죠?

애플리케이션에서 TSID 사용

유틸리티 를 통해 제공되는 기본 TSID 팩터리는 동기화된 임의 값 생성기와 함께 제공되므로 다음과 같은 최적화를 제공하는 TsidCreator사용자 정의를 사용하는 것이 좋습니다 .TsidFactory

  • 를 사용하여 임의의 값을 생성할 수 있으므로 ThreadLocalRandom동기화된 블록에서 스레드 차단을 방지합니다.
  • 적은 수의 노드 비트를 사용할 수 있으므로 임의 생성 수치에 더 많은 비트를 남길 수 있습니다.

따라서 새 개체를 생성할 때마다 사용할 수 있도록 TsidUtil다음 을 정의할 수 있습니다 .TsidFactoryTsid

public static class TsidUtil {
    public static final String TSID_NODE_COUNT_PROPERTY =
        "tsid.node.count";
    public static final String TSID_NODE_COUNT_ENV =
        "TSID_NODE_COUNT";

    public static TsidFactory TSID_FACTORY;

    static {
        String nodeCountSetting = System.getProperty(
            TSID_NODE_COUNT_PROPERTY
        );
        if(nodeCountSetting == null) {
            nodeCountSetting = System.getenv(
                TSID_NODE_COUNT_ENV
            );
        }

        int nodeCount = nodeCountSetting != null ?
            Integer.parseInt(nodeCountSetting) :
            256;

        int nodeBits = (int) (Math.log(nodeCount) / Math.log(2));

        TSID_FACTORY = TsidFactory.builder()
            .withRandomFunction(length -> {
                final byte[] bytes = new byte[length];
                ThreadLocalRandom.current().nextBytes(bytes);
                return bytes;
            })
            .withNodeBits(nodeBits)
            .build();
    }
}

그리고 동일한 애플리케이션 노드에서 여러 스레드를 사용하는 경우에도 충돌이 발생하지 않는다는 것을 입증하기 위해 다음 테스트 사례를 사용할 수 있습니다.

int threadCount = 16;
int iterationCount = 100_000;

CountDownLatch endLatch = new CountDownLatch(threadCount);

ConcurrentMap<Tsid, Integer> tsidMap = new ConcurrentHashMap<>();

long startNanos = System.nanoTime();

for (int i = 0; i < threadCount; i++) {
    final int threadId = i;
    new Thread(() -> {
        for (int j = 0; j < iterationCount; j++) {
            Tsid tsid = TsidUtil.TSID_FACTORY.create();
            assertNull(
                "TSID collision detected",
                tsidMap.put(
                    tsid,
                    (threadId * iterationCount) + j
                )
            );
        }

        endLatch.countDown();
    }).start();
}

LOGGER.info("Starting threads");
endLatch.await();

LOGGER.info(
    "{} threads generated {} TSIDs in {} ms",
    threadCount,
    new DecimalFormat("###,###,###").format(
        threadCount * iterationCount
    ),
    TimeUnit.NANOSECONDS.toMillis(
        System.nanoTime() - startNanos
    )
);

이 테스트를 실행하면 다음과 같은 결과를 얻습니다.

16 threads generated 1,600,000 TSIDs in 781 ms

ISID 생성은 충돌이 없었을 뿐만 아니라 800밀리초 이내에 160만 개의 ID를 생성했습니다.

결론

표준 UUID를 기본 키 값으로 사용하는 것은 첫 번째 바이트가 단조롭게 증가하지 않는 한 좋은 생각이 아닙니다.

이러한 이유로 시간 정렬된 TSID를 사용하는 것이 훨씬 더 좋은 생각입니다. 표준 UUID로 절반의 바이트 수를 필요로 할 뿐만 아니라 B+Tree 인덱스 키로 더 적합합니다.

SQL Server는 NEWSEQUENTIALID 를 통해 시간 정렬된 GUID를 제공하지만 GUID의 크기는 128비트이므로 TSID보다 두 배 큽니다.

동일한 문제는 시간 정렬된 UUID를 제공하는 UUID 사양 버전 7 에서도 발생합니다. 그러나 너무 큰 동일한 표준 형식(128비트)을 사용합니다. 기본 키 열 저장소의 영향은 참조하는 모든 외래 키 열에 의해 증폭됩니다.

모든 기본 키가 128비트 UUID인 경우 버퍼 풀이 테이블 페이지와 인덱스 페이지를 모두 보유하므로 디스크와 데이터베이스 메모리 모두에서 기본 키 및 외래 키 인덱스에 많은 공간이 필요합니다.

Spring Framework 6.x의 새로운 기능

버전 6.0의 새로운 기능

JDK 17+ 및 Jakarta EE 9+ 기준선

  • 이제 Java 17 소스 코드 수준을 기반으로 하는 전체 프레임워크 코드베이스입니다.
  • Servlet, JPA 등의 네임스페이스를 위해 javax 에서 jakarta로 마이그레이션됩니다
  • Jakarta EE 9 및 Jakarta EE 10 API와의 런타임 호환성.
  • 최신 웹 서버와 호환: Tomcat 10.1 , Jetty 11 , Undertow 2.3 .
  • 가상 스레드 와의 초기 호환성 (JDK 19 현재 미리 보기).

일반 핵심 개정

코어 컨테이너

  • 기본적으로 java.beans.Introspector 없이 기본 빈 속성 결정됩니다. 
  • GenericApplicationContext(refreshForAotProcessing) 에서 AOT 처리 지원.
  • 미리 해결된 생성자와 팩토리 메서드를 기반으로 한 빈 정의 변환.
  • AOP 프록시 및 구성 클래스에 대한 초기 프록시 클래스 결정을 지원합니다.
  • PathMatchingResourcePatternResolver가 NIO 및 모듈 경로 API를 스캔에 사용하여 각각 GraalVM 네이티브 이미지 및 Java 모듈 경로 내에서 클래스 경로 스캔을 지원합니다.
  • DefaultFormattingConversionService가 ISO 기반 기본 java.time유형 구문 분석을 지원합니다.

데이터 액세스 및 트랜잭션

  • 사전 결정 JPA 관리 유형 지원(AOT 처리에 포함).
  • Hibernate ORM 6.1 에 대한 JPA 지원 (Hibernate ORM 5.6과의 호환성 유지).
  • R2DBC 1.0 으로 업그레이드합니다 (R2DBC 트랜잭션 정의 포함).
  • JCA CCI 지원 제거.

스프링 메시징

  • @RSocketExchange 서비스 인터페이스 를 기반으로 하는 RSocket 인터페이스 클라이언트 .
  • Netty 5 알파 를 기반으로 하는 Reactor Netty 2를 조기에 지원합니다 .
  • Jakarta WebSocket 2.1 및 표준 WebSocket 프로토콜 업그레이드 메커니즘을 지원합니다.

일반 웹 개정

스프링 MVC

  • PathPatternParser기본적으로 사용됩니다( 옵트인 기능 PathMatcher포함).
  • 오래된 타일 및 FreeMarker JSP 지원 제거.

스프링 WebFlux

  • 멀티파트 양식 업로드를 스트리밍하는 새로운 PartEvent API( 클라이언트 및 서버 모두 ).
  • ResponseEntityExceptionHandler WebFlux 예외를 사용자 지정하고 RFC 7807 오류 응답 을 렌더링하는 새로운 기능 입니다.
  • Flux비스트리밍 미디어 유형에 대한 반환 값(더 이상 List작성 전으로 수집되지 않음).
  • Netty 5 알파 를 기반으로 하는 Reactor Netty 2를 조기에 지원합니다 .
  • JDK HttpClient는 WebClient.

관찰 가능성

Spring Framework의 여러 부분에서 Micrometer Observation 을 사용한 Direct Observability instrumentation . spring-web모듈은 이제 컴파일 종속성으로 필요 합니다 io.micrometer:micrometer-observation:1.10+.

  • RestTemplateHTTP 클라이언트 요청 관찰 을 WebClient생성하도록 계측됩니다.
  • Spring MVC는 새로운 org.springframework.web.filter.ServerHttpObservationFilter.
  • Spring WebFlux는 새로운 org.springframework.web.filter.reactive.ServerHttpObservationFilter.
  • 컨트롤러 메서드 에 대한 마이크로미터 컨텍스트 전파Flux 및 반환 값과의 통합.Mono

테스트

  • JVM 또는 GraalVM 네이티브 이미지 내에서 AOT 처리 애플리케이션 컨텍스트 테스트를 지원합니다.
  • HtmlUnit 2.64+ 요청 매개변수 처리와 통합.
  • 서블릿 모의( MockHttpServletRequestMockHttpSession)는 이제 서블릿 API 6.0을 기반으로 합니다.

Spring Data JPA를 사용할 때의 6가지 성능 함정

토르벤 얀센의 아티클 중 참고할 만한 내용을 퍼왔습니다.
https://thorben-janssen.com/6-performance-pitfalls-when-using-spring-data-jpa
먼저 글의 출처를 밝힙니다
이 사이트는 Hibernate와 JPA 의 최신 동향 정보를 확인할 수 있는 괜찮은 사이트입니다.

이 아티클의 내용은 JPA를 사용할 때 성능 문제를 회피하기 위한 6가지 함정을 소개하고 있습니다. 필요한 경우에는 어쩔 수 없이 써야겠지만 함정에 빠지지 않도록 조심해야겠네요.

Spring Data JPA는 퍼시스턴스 레이어를 쉽게 구현할 수 있기 때문에 인기를 얻었습니다. 그러나 여전히 Spring Data JPA, JPA 구현 및 데이터베이스 작동 방식을 이해해야 합니다. 이를 무시하는 것은 성능 문제의 가장 일반적인 이유 중 하나이며 이 기사에서 설명하는 성능 함정에 빠지게 됩니다.

이러한 함정의 대부분을 피하려면 먼저 좋은 로깅 구성을 활성화해야 합니다. Hibernate의 통계는 실행된 모든 작업에 대한 개요를 제공하고 기록된 SQL 문은 데이터베이스와 상호 작용하는 방법을 보여줍니다.

업데이트 작업을 구현할 때 Spring Data JPA의 JpaRepository 에서 제공하는 saveAndFlush 메소드 호출을 피해야 합니다. 그리고 새로운 엔티티에 대한 기본 키 값을 생성할 때, GenerationType.SEQUENCE 를 선호하고  @SequenceGenerator 를 정의하여 Hibernate의 성능 최적화의 이점을 얻어야 합니다. 데이터베이스가 시퀀스를 지원하지 않는다면, Hibernate가 비효율적인 테이블 전략을 사용하는 것을 방지하기 위해 GenerationType.IDENTITY 를 명시적으로 정의해야 합니다.

엔터티 개체를 읽을 때 최상의 성능을 얻으려면 모든 연결의  FetchType  을 LAZY로 정의해야 합니다. 사용 사례에서 엔터티 간의 연결을 트래버스해야 하는 경우 엔터티 개체를 가져올 때 연결을 초기화하도록 지속성 공급자에게 알려야 합니다. 그렇게 하려면 쿼리에 JOIN FETCH 절을 추가하거나 @EntityGraph 주석으로 저장소 메서드에 주석을 추가할 수 있습니다.

또한 읽기 전용 작업에 DTO 프로젝션을 사용하는 경우 연결을 포함하는 복잡한 인터페이스 기반 DTO 프로젝션을 피해야 합니다. 그들은 Spring Data JPA가 DTO 프로젝션 대신 엔티티 프로젝션을 사용하도록 강제합니다.

Flutter 1

1. Flutter의 UI 구조

  • Flutter는 모두 위젯으로 이루어져 있다.
  • 텍스트나 버튼 혹은 레이아웃 등 UI 가 관련된 무엇을 만들던 UI가 위젯으로 생성 (위젯 트리)
image
  • Row Column Container 이 3가지 위젯이 위젯 트리를 구성
    • 보이는 위젯들을 제어하고, 제한하며, 정렬시킴
    • 마지막 지점은 항상 눈에 보이는 UI요소로 되어있음 (Text, Icon 등..)
    • Row는 가로로 하위 위젯을 생성
    • Columnd은 세로로 하위 위젯을 생성
    • Container는 자식 위젯들을 커스터마이징할 수 있는 위젯 클래스(여백, 간격, 테두리, 배경색 등)

2. Flutter 위젯 생성 및 위젯 데이터 변경 적용

  • StatelessWidget, StatefulWidget, State

2.1 StatelessWidget

```dart
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    )
  }
}
```
  • StatelessWidget을 클래스에 상속 받아 사용하기위해 build()함수를 override해서 사용
  • bulid() 함수를 통해서 위젯을 생성
  • Flutter는 State를 통해서 위젯을 변경, State이 없다면 위젯을 변경할 수가 없음
  • StatelessWidget은 State이 없는 위젯을 생성하기 때문에 위젯의 데이터를 변경할 수 없음
  • StatelessWidget
    • 변경 가능한 상태가 필요하지 않는 위젯
    • State 이 없는 Widget을 정의할 때 사용한다.
    • 생명주기가 존재하지 않으며 bulid 함수 한번만 실행된다.

2.2 StatefulWidget , State

  • StatefulWidget은 StatelessWidget과 반대로 State을 가진 위젯을 생성하기 때문에 State에 따라 위젯을 변경함
  • StatefulWidget사용할때 State는 기본적으로 동시에 같이 사용해야함
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
      final String title;
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 1;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Container();
      }
    }
    
  • State은 한마디로 Data라고 정의할 수 있음
  • StatefulWidget
    • 변할 수 있는 스텟이다. state에 대한 configration을 설정한다.
    • 클래스내의 필드는 항상 final 로 표시된다.
    • State 에 따른 UI 변화가 필요할 때 사용한다.
    • State 과 같이 사용된다.
  • State
    • 위젯이 빌드될때 동기적으로 읽을 수 있음
    • 위젯의 생명주기 동안 변경될 수 있는 정보
    • StatefulWidget 의 생명 주기를 가짐
  • 성능을 위해 StatefulWidget, State분리
    • 속성 또는 부모 위젯이 바뀌게 되면 StatefulWidget 바뀌게 되는데, 이 과정에서 위젯이 재생성하는데 StatefulWidget이 라이프사이클을 가지고 있으면 라이프사이클을 다시 복구하는데 꽤나 많은 성능이 필요함
      • State가 라이프사이클을 관리하면 생명주기가 위젯과 함께 폐기되었다가 다시 재생성 되지 않음
      • State 자체는 폐기되지 않아서 데이터 변경에 대한 응답으로 필요할 때 언제든지 위젯을 만들 수 있음

탄력적인 장애 제어[3](feat.Resilience4jFeign)

Resilience4jFeign 설정

1. Resilience4jFeign 이란?

  • Retrofit , JAXRS-2.0 및 WebSocket 에서 영감을 받은 Java to HTTP 클라이언트 바인더
    • 텍스트 기반 API 지원으로 제한되지만 요청 재생과 같은 시스템 측면을 크게 단순화 함
    • 작동법
    • 주석을 템플릿화된 요청으로 처리하여 작동됨
    • 사용법
    • Interface 작성하고 Annotation을 선언만으로 사용가능
    • Java Version
    • Feign 10.x 이상은 Java 8을 기반으로 하며 Java 9, 10 및 11에서 작동해야 함
    • Feign 9.x JDK 6 사용
    • Feign 로드맵

      원본

2. Interface 작성

  • 데코레이터는 클래스를 사용하여 만들 수 있고, 결합하여 사용 가능
    아래 코드는 CircuitBreaker, RateLimiter 결합하여 사용한 경우

    
    public interface MyService {
            @RequestLine("GET /greeting")
            String getGreeting();
    
            @RequestLine("POST /greeting")
            String createGreeting();
        }
    
        CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
        RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");
    
        FeignDecorators decorators = FeignDecorators.builder()
                                         .withRateLimiter(rateLimiter)
                                         .withCircuitBreaker(circuitBreaker)
                                         .build();
    
        MyService myService = Resilience4jFeign.builder(decorators).target(MyService.class, "http://localhost:8080/");
    ```</br>
  • 데코레이터가 쌓이는 방식

    • 메서드를 호출하면 CircuitBreaker -> RateLimiter -> Feign 순으로 동작하고 영향을 미치는 경우
      RuntimeException이 발생 합니다.



      * 데코레이터가 쌓이는 다이어그램
  • 데코레이터 주의사항

    • 데코레이터가 적용되는 순서는 선언된 순서와 일치 하므로 주의 필요
    // Atype 데코이터 쌓이는 순서 : circuitBreaker -> rateLimiter -> Feign
    FeignDecorators decoratorsA = FeignDecorators.builder()
                                        .withCircuitBreaker(circuitBreaker)
                                        .withRateLimiter(rateLimiter)
                                        .build();
    
    // Btype 데코이터 쌓이는 순서 : rateLimiter -> circuitBreaker -> Feign
    FeignDecorators decoratorsB = FeignDecorators.builder()
                                        .withRateLimiter(rateLimiter)
                                        .withCircuitBreaker(circuitBreaker)
                                        .build();

3. JacksonEncoder/Decoder 포함

    public interface MyService {
            @RequestLine("GET /greeting")
            String getGreeting();
            @RequestLine("POST /greeting")
            String createGreeting();
        }

    CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("backendName");
    RateLimiter rateLimiter = RateLimiter.ofDefaults("backendName");

    FeignDecorators decorators = FeignDecorators.builder()
                                        .withCircuitBreaker(circuitBreaker)
                                        .withRateLimiter(rateLimiter)
                                        .build();

    this.MyService = Resilience4jFeign.builder(decorators)
                     .encoder(new JacksonEncoder())
                     .decoder(new JacksonDecoder())
                     .target(MyService.class, "http://localhost:8080/"))

대화하기