no image
[TestCode] JUnit5 기본 메뉴얼
프로젝트가 작을 때는 그냥 테스트 코드는 선택인 줄 알고 그냥 소홀히 했다. 그러나 하면 할 수록 테스트 코드가 중요하다는 걸 몸으로 느끼고 많은 개발자들이 테스트 코드에 대해서 강조하신다. 그래서 그냥 공부만 하려고 했으나 정리를 해놓으면 더 깔끔하게 머릿속에 들어올 거 같아서 정리하려고 한다. 🤖테스트 코드와 테스트 메서드 테스트 클래스란 최상위 클래스 , static member class, @Nested 클래스에 적어도 한 개의 @Test 어노테이션이 달린 테스트 메서드가 포함돼있는 걸 말한다. 테스트 클래스는 추상 클래스면 안되고 하나의 생성자가 있어야 한다. 테스트 메서드란 @Test @RepeatTest @ParamterizedTest @TestFactory @TestTemplate 같은 메..
2021.12.05
no image
[Querydsl] QueryDSL 사용법 정리
이번 포스팅은 프로젝트에서 QueryDSL을 사용하면서 가끔 잊어버리는 것이 있어서 전체적으로 정리해보려 한다. ➡️QueryDSL 원하는 조건의 데이터를 수집하기 위해서는 필연적으로 JPQL을 작성하게 된다. 만약 이런과정에서 로직이 복잡한 로직의 경우 개행이 포함된 쿼리 문자열이 떨어지고 가독성이 떨어진다. JPQL 문자열에 오류가 있을 경우 정적 쿼리라면 어플리케이션 로딩 시점에 이를 발견할 수 있으나 런타임 시점에 에러가 발생한다. 이러한 문제를 QueryDSL이 해결을 해준다. QueryDSL은 정적 타입을 이용해서 SQL 등의 쿼리를 생성해주는 프레임워크이다. ➡️QueryDSL의 장점 문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문제 오류를 빠르게 찾을 수 있다. 동적인 쿼리 작성..
2021.12.03
no image
[Design Pattern] 빌더 패턴(Builder Pattern)
🛠 Builder Pattern 이란 객체의 생성 단계들을 캡슐화하여 객체의 생성을 유연하게 해주는 패턴입니다. 그래서 빌더 패턴은 생성자에 들어갈 매개 변수가 많든 적든 차례대로 매개 변수를 받아들이고 모든 매개 변수를 받은 뒤에 이 변수들을 통합해서 한 번에 사용합니다. 🛠 Builder Pattern Builder Pattern의 장점에 대한 예시를 알아보자. Builder Pattern의 장점은 다음과 같은 것들이 있다. 가독성을 높일 수 있다. 불변성을 확보할 수 있다. 필요한 데이터만 설정이 가능하다. 이제 예시를 차근차근 보자 🛠첫번째로 어떤 측면에서 가독성을 높여주는지 알아보자. public class User { private Long id; private String email; pri..
2021.12.02
no image
[GIT] git-flow 전략
Git으로 프로젝트를 진행하면서 Git-flow 전략이 어떻게 사용되는지 알아보려고 한다. 📁GIt-flow Git-flow는 10년전 쯤에 Vincent Driessen이라는 사람의 블로그 글에 널리 퍼지기 시작했고 현재는 Git으로 개발할 때 거의 표준과 같이 사용되는 방법론이다. Git-flow는 기능이 아니고 서로간의 약속인 방법론이다. 그렇다고 방법론을 그대로 사용해도 되지만 각자 개발 환경에 따라 수정하고 변형해서 사용하라고 언급하였습니다. Git-flow는 총 5가지의 브랜치를 사용해서 운영을 합니다. master : 기준이 되는 브랜치로 제품을 배포하는 브랜치 입니다. develop : 개발 브랜치로 개발자들이 이 브랜치를 기준으로 각자 작업한 기능들을 합(Merge)칩니다. feature..
2021.11.14
no image
[프로그래밍] 애자일 소프트웨어 개발
공부하다가 우연히 저번에 다른분이 쓰신 블로그에 있는 애자일 소프트웨어 개발에 대해서 궁금해서 한번 정리 해볼려고한다. 💡애자일 소프트웨어 개발? 애자일 방법론은 소프트웨어 개발 방법에 있어서 아무런 계획이 없는 개발 방법과 계획이 지나치게 많은 개발 방법들 사이에서 타협점을 찾고자 하는 방법론이다. 💡애자일 소프트웨어 개발 선언 4가지 공정과 도구 보다 개인과 상호 작용을 포괄적인 문서보다 작동하는 소프트웨어를 계약 협상보다 고객과 협력을 계획을 따르기보다 변화에 대응하기를 가치 있게 여긴다. 💡애자일 선언 이면의 12가지 원칙 우리의 최우선 순위는 가치 있는 소프트웨어를 일찍 그리고 지속적으로 전달해서 고객을 만족시키는 것이다. 비록 개발의 후반부일지라도 요구사항 변경을 환영하라.애자일 프로세스들은 ..
2021.11.13
Dev
no image
[Spring] DTO 사용범위에 대하여
프로젝트를 진행하던 중 DTO를 개념만 알고 어디서 사용해야 적절한지에 대해서 고민해보지 않고 사용해서 정리를 해보려고 한다. 그래서 어디서 사용해야 적절한 것인지 알아보려 한다. ➡️DTO DTO(Data Transfer Object)란 계층 간 데이터 교환을 위해 사용하는 객체이다. MVC패턴으로 예를 들어보자 MVC 패턴은 애플리케이션을 개발할 때 그 구성요소를 Model과 View 및 Controller 등 세 가지 역할로 구분하는 디자인 패턴이다. 비즈니스 처리 로직(Model)과 UI영역(View)은 서로의 존재를 모르고 Controller가 중간에서 Model과 View를 연결하는 역할을 한다. Controller는 View로부터 들어온 사용자 요청을 해석하여 Model을 업데이트하거나 Mo..
2021.11.12
no image
[AWS]RDS Mysql Replication 설정 (SpringBoot + JPA + Mysql)
📁DB Replication https://jarvics.tistory.com/68 [MYSQL]효율적인 트래픽 분산을 위한 Master/Slave 동적 라우팅 사용자가 지속적으로 증가하면 많은 양의 트래픽이 발생한다. 그러므로 하나의 DB서버로 쓰기와 읽기 작업이 모두 진행된다면 쉽게 DB서버에 부하가 발생할 수 있다. 이 문제에 대한 해결책으로 jarvics.tistory.com 데이터베이스 이중화 방식 중 하나로 Master DB + Slave DB로 구성한다. Master DB에 데이터 변경이 감지되면 Master DB 로그를 기반으로 Slave DB에 복제한다. Master DB에는 데이터 변경이 필요한 INSERT,DELETE,UPDATE 등의 쿼리가 사용하고, Slave DB에는 SELECT..
2021.11.10
no image
[Server] CI/CD
➡️CI (Continious Integration) CI는 Continious Integration의 약자이며, 지속적 통합이라는 의미이다. 지속적 통합이란, 애플리케이션의 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트 되어 공유 레포지토리에 통합하는 것을 의미한다. CI는 지속적으로 서비스해야 하는 애플리케이션은 기능 추가 시마다 커밋을 날려 저장소에 버전을 업데이트 해야한다. 여러명의 사람이 한팀으로 작업을 할 경우 많은 커밋들이 쌓이게 된다. 그럴 때마다 , 기능 별로 빌드/테스트/병합을 한다면 너무 번거로운 작업이 된다.이러한 상황에서 자동화된 빌드 & 테스트는 소스코드의 충돌 등을 방어하는 이익을 제공할 수 있다. CI는 MSA환경에서는 대부분 소규모 기능 단위로 빠르게 개발 & 적용을 ..
2021.11.08
728x90

프로젝트가 작을 때는 그냥 테스트 코드는 선택인 줄 알고 그냥 소홀히 했다.

그러나 하면 할 수록 테스트 코드가 중요하다는 걸 몸으로 느끼고 많은 개발자들이 테스트 코드에 대해서 강조하신다. 그래서 그냥 공부만 하려고 했으나 정리를 해놓으면 더 깔끔하게 머릿속에 들어올 거 같아서 정리하려고 한다.

 

🤖테스트 코드와  테스트 메서드

  • 테스트 클래스란
    • 최상위 클래스 , static member class, @Nested 클래스에 적어도 한 개의 @Test 어노테이션이 달린 테스트 메서드가 포함돼있는 걸 말한다. 테스트 클래스는 추상 클래스면 안되고 하나의 생성자가 있어야 한다.
  • 테스트 메서드란 
    • @Test @RepeatTest @ParamterizedTest @TestFactory @TestTemplate 같은 메타 어노테이션이 메서드에 붙여진 메서드를 말한다.
    • 테스트 메서드와 라이프사이클 메서드는 테스트를 진행할 클래스, 상속한 부모 클래스 또는 인터페이스에 선언된다. 그리고 테스트 메서드와 라이프 사이클 메서드는 추상 클래스 선언을 하면 안 되고, 어떠한 값도 리턴되어선 안된다.

 

 

 

🔬JUnit 

JUnit은 단위 테스트를 위한 테스트용 프레임워크이다.  JUnit 홈페이지에 JUnit5는 JUnit Platform, JUnit Jupiter, JUnit Vintage가 합쳐진 것이라고 한다.

 

  • JUnit Platform
    • JUnit Platform은 JVM에서 테스트 프레임워크를 실행하는데 기초를 제공하고, TestEngine API를 제공해 테스트 프레임워크를 개발할 수 있다.
  • JUnit Jupiter
    • JUnit Jupiter는 JUnit 5에서 테스트를 작성하고 확장을 하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합이다.
  • JUnit Vintage
    • JUnit Vintage는 하위 호환성을 위해 JUnit 3 ,JUnit 4를 기반으로 돌아가는 플랫폼에 테스트 엔진을 제공해준다.

 

JUnit 5 사용 시 Java 8부터 지원하기 때문에 그 이상의 버전으로 진행해야 한다.

 


🔬테스트 코드 작성

위 예제는 테스트 코드 작성 예시로 최소 조건으로 작성한 것이다.

테스트 코드는 테스트를 구성하고 프레임워크를 상속하기 위해서 다양한 어노테이션을 제공한다.

 

  • @DisplayName : 테스트 클래스 또는 메서드에 이름을 붙여줄 때 사용한다. 테스트 코드에서는 메서드명이 한국어로 가능하지만 이 어노테이션으로 작성하면 내가 생각하기에는 깔끔하다고 생각한다.

 

이런 식으로 이름이 자신이 @DisplayName으로 설정한 값이 따라온다.

  • @DisplayNameGeneration : @Test 메서드 이름에 언더 바로 표시된 부분은 공백으로 처리된다.
  • @BeforeEach : 각 테스트 메서드가 실행되기 전에 실행되어야 하는 메서드를 명시해준다. 테스트 전에 필요한 데이터를 셋업 할 때 많이 사용한다. 

이처럼 메서드에 미리 데이터를 넣어 값을 미리 세팅한 다음 테스트를 진행할 수 있다.

  • @BeforeAll : @BeforeEach는 각 메서드 실행 전 실행되지만 이 메서드는 테스트 시작 전 딱 한 번만 실행된다.
  • @AfterEach : @BeforeEach와 원리는 동일하다 테스트가 끝날 때마다 실행된다. 이 메서드는 혹여나 데이터를 삽입하는데 중복되거나 하는 우려를 해결하기 위해 테스트 끝나면 데이터를 삭제해줄 때 사용한다.

  • @AfterAll : @BeforeAll가 원리가 같다. 테스트가 완전히 끝난 후 한번 실행된다.
  • @Disable :  테스트 클래스나, 테스트 메서드를 비활성화한다. JUnit 4에서는 @Ignore과 같은 역할이다.
  • @Tag : 테스트를 필터링할 때 사용한다. 클래스 또는 메서드 레벨에 사용한다.
  • @ExtendWith : extension을 등록한다. 이 어노테이션은 상속이 가능하다.
  • @RegisterExtension : 필드를 통해 extension을 등록한다. private이 아니라면 상속된다.
  • @TimeOut : 주어진 시간 안에 테스트가 끝나지 않으면 테스트에 실패한다.

이런 식으로 메서드 시간제한을 둘 수 있다. 위 메서드는 2초 안에 테스트가 끝나지 않으면 실패한다.

 

 

🧑🏻‍💻예제 코드 : https://github.com/ryudongjae/blog-ex

 

GitHub - ryudongjae/blog-ex: 📁블로그 예제 코드

📁블로그 예제 코드 . Contribute to ryudongjae/blog-ex development by creating an account on GitHub.

github.com


👍🏻REFERENCE

https://donghyeon.dev/junit/2021/04/11/JUnit5-완벽-가이드/

 

JUnit5 완벽 가이드

시작하기전

donghyeon.dev

 

728x90

'Dev > TestCode' 카테고리의 다른 글

[TestCode] AssertJ에 대하여(JUnit5)  (0) 2021.12.07
728x90

이번 포스팅은 프로젝트에서 QueryDSL을 사용하면서 가끔 잊어버리는 것이 있어서 전체적으로 정리해보려 한다.

➡️QueryDSL

원하는 조건의 데이터를 수집하기 위해서는 필연적으로 JPQL을 작성하게 된다. 만약 이런과정에서 로직이 복잡한 로직의 경우 개행이 포함된 쿼리 문자열이 떨어지고 가독성이 떨어진다. JPQL 문자열에 오류가 있을 경우 정적 쿼리라면 어플리케이션 로딩 시점에 이를 발견할 수 있으나 런타임 시점에 에러가 발생한다. 이러한 문제를 QueryDSL이 해결을 해준다.

QueryDSL은 정적 타입을 이용해서 SQL 등의 쿼리를 생성해주는 프레임워크이다.

 

➡️QueryDSL의 장점

문자가 아닌 코드로 쿼리를 작성함으로써, 컴파일 시점에 문제 오류를 빠르게 찾을 수 있다.

동적인 쿼리 작성이 편리하다.

쿼리 작성 시 제약 조건 등을 메서드 추출을 통해 재사용할 수 있다.

 


⚙️설정

build.gradle

plugins {
	id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

group = 'com'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}
ext {
	set('snippetsDir', file("build/generated-snippets"))
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'com.querydsl:querydsl-jpa'
	runtimeOnly 'com.h2database:h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	outputs.dir snippetsDir
	useJUnitPlatform()
}

def querydslDir = "$buildDir/generated/querydsl"
querydsl {
	jpa = true
	querydslSourcesDir = querydslDir
}
sourceSets {
	main.java.srcDir querydslDir
}
configurations {
	querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
	options.annotationProcessorPath = configurations.querydsl
}

def querydslDir은 Q-class가 생성되는 경로이다.

 

Q-type class는 QueryDSL 설정을 성공적으로 마치면 오른쪽 상단 Gradle에 들어가 Task를 선택 후 other에 들어가 complieQueryDSL를 선택하면 @Entity가 붙은 클래스에 자동으로 생성된다.

이런식으로 생성된다.

 

Q-type의 클래스는 QueryDSL을 사용하여 메소드 기반으로 쿼리를 작성할 때 우리가 만든 도메인 클래스의 구조를 설명해주는 메타데이터 역할을 하며 쿼리의 조건을 설정할 때 사용된다.

 


이제 QueryDSL의 기본적인 문법을 보자.

 

1.sort

  • 내림차순 정렬(desc)
List<Post> descPostId = jpaQueryFactory
        .selectFrom(post)
        .orderBy(post.id.desc())
        .fetch();
  • 오름차순 정렬(asc)
List<Post> ascPostId = jpaQueryFactory
        .selectFrom(post)
        .orderBy(post.id.asc())
        .fetch();
  • 여러 조건으로 정렬 
List<Post> resultPost = jpaQueryFactory
        .selectFrom(post)
        .orderBy(post.id.desc(), post.title.asc())
        .fetch();
  • null 처리
List<Post> result= jpaQueryFactory
        .selectFrom(post)
        .orderBy(post.id.desc(), post.title.asc().nullsLast)
        .fetch();

 

 

2.fetch

  • fetch()
    • 리스트로 반환하는 방법이고 만약 리스트가 비어있으면 빈 리스트를 반환한다.
List<Post> fetchList = jpaQueryFactory
        .selectFrom(post)
        .fetch();
  • fetchOne()
    •  하나를 조회할 때 사용한다. 결과가 없을 경우 null을 반환하고 둘 이상일 경우에는 NonUniqueReultException을 반환한다.
Post fetchOne = jpaQueryFactory
        .selectFrom(post)
        .fetchOne();
  • fetchFirst()
    • 가장 처음의 한건을 가져오고 싶을때 사용한다.
Post fetchFirst = jpaQueryFactory
        .selectFrom(post)
        .fetchFirst();
  • fetchResults()
    •  페이징을 위해 사용되고 total content를 가져온다.
QueryResults<Post> fetchResult = jpaQueryFactory
        .selectFrom(post)
        .fetchResult();
  • fetchCount()
    •  개수조회에 사용된다.
long fetchCount = jpaQueryFactory
        .selectFrom(post)
        .fetchCount();

 

3.paging

querydsl에서는 페이징에 필요한 정보를 가져올 수 있는 메서드가 존재한다.

QueryResults<Post> result = jpaQueryFactory
        .selectFrom(post)
        .orderBy(post.id.desc())
        .offset(0)
        .limit(3)
        .fetchResult();
        
long total =  result.getTotal();
long limit = result.getLimit();
long offset = result.getOffset();
List<Post> results = result.getResults();

간단하게 메서드를 설명하자면  

total은 전체 컨텐츠 갯수이고,

offset은 조회를 시작할 위치이고,

limit은 조회 개수를 제한할 위치이다.

.getResults() 메서드는 조건에 맞게 조회된 컨텐츠 리스트이다.

4. aggregation

querydsl에서 편하게 sql 집계를 낼수 있는 기능이 있다. 실제 sql을 사용하는 것처럼 sql의 groupBy.having절을 사용할 수 있다.

List<Tuple> result = jpaQueryFactory
		.select(
        		team.name,
        		user.count,
                user.age.avg(),
                user.age.min(),
                user.age.max()
         )
        .from(user)
        .join(user.team, team)
        .groupBy(team.name)
        .having(team.name.eq("A")
        .fetch();
        
Tuple team1 = result.get(0);

String teamName = team1.get(team.name);
Long team1Cnt = team1.get(user.count());
Double team1AgeAvg = team1.get(user.age.avg());
Integer team1MinAge = team1.get(user.age.min());
Integer team1MaxAge = team1.get(user.age.max());

 

😸GITHUB

https://github.com/ryudongjae/Querydsl

 

GitHub - ryudongjae/Querydsl

Contribute to ryudongjae/Querydsl development by creating an account on GitHub.

github.com


REFERENCE

https://devkingdom.tistory.com/243

https://joont92.github.io/jpa/QueryDSL/

728x90
728x90

🛠 Builder Pattern 이란

객체의 생성 단계들을 캡슐화하여 객체의 생성을 유연하게 해주는 패턴입니다. 그래서 빌더 패턴은 생성자에 들어갈 매개 변수가 많든 적든 차례대로 매개 변수를 받아들이고 모든 매개 변수를 받은 뒤에  이 변수들을 통합해서 한 번에 사용합니다.

 

🛠 Builder Pattern

Builder Pattern의 장점에 대한 예시를 알아보자. Builder Pattern의 장점은 다음과 같은 것들이 있다.

  • 가독성을 높일 수 있다.
  • 불변성을 확보할 수 있다.
  • 필요한 데이터만 설정이 가능하다.

이제 예시를 차근차근 보자

 

🛠첫번째로 어떤 측면에서 가독성을 높여주는지 알아보자.

public class User {

    private Long id;
    private String email;
    private String name;
    private String password;
    private int age;

    public User(Long id, String email, String name, String password, int age) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.password = password;
        this.age = age;
    }
}

우선 기본적인 생성자로 넘기는 방법이다. 

User user = new User(1L,"rrr11@naver.com","KK","l12313123",14);

 

모든 데이터를 생성자로 넘기면 위처럼 작성하다 보면 인자가 많아지면  순서를 바꿔 넣을 수도 있고 직관적으로 한눈에 알아보기 어렵다는 단점이 존재한다. 이제 이 문제를 점차 해결해보자.

 

  @Builder
    public User(Long id, String email, String name, String password, int age) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.password = password;
        this.age = age;
    }

 

생성자를 위처럼 빌더 패턴으로 넘기면 값을 설정할 때 

	User user = User.builder()
                .id(1L)
                .email("rrr11@naver.com")
                .name("KK")
                .password("l12313123")
                .age(14)
                .build();

 

이런식으로 한눈에 직관적으로 보이게 값을 삽입할 수 있다.


🛠두번째로 불변성을 확보하는 예시를 보자 

 

자바 빈 패턴으로 @Setter로 값을 삽입할 수 있는데 이 방식은 가독성도 생성자 패턴보다 좋아지고 객체를 생성하기에도 편해졌지만, 함수 호출 한 번으로 객체를 생성할 수 없고  객체 일관성이 일시적으로 깨질 수 있다.

 

@Setter
public class User {

    private Long id;
    private String email;
    private String name;
    private String password;
    private int age;
    
  }
  
  
   User user = new User();
        user.setId(1L);
        user.setEmail("rrr11@naver.com");
        user.setName("KK");
        user.setPassword("l12313123");
        user.setAge(23);

위처럼 @Setter를 열어두는 것은 불필요하게 확장 가능성을 열어두는 것이기 때문에 Open-Close 법칙에도 위배가 되고, 불필요한 코드 리딩을 유발한다.

 


🛠세번째로 필요한 데이터를 설정하는 예시를 보자.

 

우선 데이터를 전송할 때 그 작업에 필요한 데이터만 넘길 수 있다. 필요한 데이터만 넘기게 되면 유저 정보의 패스워드 등을 노출하지 않을 수 있기 때문에 보안적으로 이점을 가진다.

일단 패스워드와 아이디는 넘기면 안된다고 가정을 해보자.

기본적인 생성자로 넘기면  우선 모든 데이터가 넘어간다. 이 문제를 생성자로 해결해보자.

public class User {

    private Long id;
    private String email;
    private String name;
    private String password;
    private int age;

    public User(String email, String name, int age) {
        this.email = email;
        this.name = name;
        this.age = age;
    }

    public User(Long id, String password) {
        this.id = id;
        this.password = password;
    }
}

이러한 방식으로 분리해서 하면 아이디와 패스워드를 빼고 넘길 수 있다. 이런 식으로 분리하면 코드가 지저분 해지고 가독성이 떨어진다.

빌더 패턴을 사용해보자 

    @Builder
    public User(Long id, String email, String name, String password, int age) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.password = password;
        this.age = age;
    }
	User user = User.builder()
                .email("rrr11@naver.com")
                .name("KK")
                .age(14)
                .build();

 

이런식으로 필요한 데이터만 설정할 수 있다. 그러므로 불필요한 코드를 줄여준다. 이러한 방법으로 작업마다 필요한 데이터를 엔티티객체 또는 도메인 객체로부터 DTO로 만들어 넘겨줄 수 있다. 

 

 

DTO 예시

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class UserDto {
    
    private String email;
    private String name;
    private int age;
}

이런식으로 DTO를 만들 수 있다.


그러나 빌더 패턴에 장점만 존재하는 것이 아니다. 

우선 객체를 사용하려면 빌더 객체를 생성해야 하고, 다른 패턴보다 많은 코드를 요구하기 때문에 인자가 충분히 많아진 상황에서 이용할 필요가 있다.

 

GITHUB 소스코드 : https://github.com/ryudongjae/blog-ex

 

GitHub - ryudongjae/blog-ex

Contribute to ryudongjae/blog-ex development by creating an account on GitHub.

github.com


REPERENCE

728x90

'Dev > JAVA' 카테고리의 다른 글

[JAVA]반복문 안에서 List 요소 삭제  (0) 2022.03.04
[Code] Refactoring , 리펙토링  (0) 2022.02.28
[JAVA]Optional이란?  (0) 2021.06.17
[JAVA]상속(Inheritance)  (0) 2021.06.16
[JAVA]삼항연산자(Ternary Operator)  (0) 2021.06.15

[GIT] git-flow 전략

ryudjae
|2021. 11. 14. 00:55
728x90

Git으로 프로젝트를 진행하면서 Git-flow 전략이 어떻게 사용되는지 알아보려고 한다.

 

📁GIt-flow

Git-flow는 10년전 쯤에 Vincent Driessen이라는 사람의 블로그 글에 널리 퍼지기 시작했고 현재는 Git으로 개발할 때 거의 표준과 같이 사용되는 방법론이다.

 

Git-flow는 기능이 아니고 서로간의 약속인 방법론이다. 그렇다고 방법론을 그대로 사용해도 되지만 각자 개발 환경에 따라 수정하고 변형해서 사용하라고 언급하였습니다.

 

Git-flow는 총 5가지의 브랜치를 사용해서 운영을 합니다. 

  • master : 기준이 되는 브랜치로 제품을 배포하는 브랜치 입니다. 
  • develop : 개발 브랜치로 개발자들이 이 브랜치를 기준으로 각자 작업한 기능들을 합(Merge)칩니다. 
  • feature : 단위 기능을 개발하는 브랜치로 기능 개발이 완료되면 develop 브랜치에 합칩니다.
  • release : 배포를 위해 master 브랜치로 보내기 전에 먼저 QA(품질검사)를 하기위한 브랜치 입니다. 
  • hotfix : master 브랜치로 배포를 했는데 버그가 생겼을 떄 긴급 수정하는 브랜치 입니다.

 

아래 이미지가 Git-flow를 설명할때 가장 적절한 이미지인거 같다.

  1. 우선 master 브랜치에서 시작한다.
  2. 동일한 브랜치를 develop에도 생성을 한다. 이 develop브랜치에서 개발자들이 개발을 진행한다.
  3. 개발을 하다가 기능 구현이 필요한 경우 feature 브랜치를 생성해서 기능을 구현하고 다른 개발자가 다른 기능을 구현 할때 또 새로운 브랜치를 만들어 진행한다.
  4. feature 브랜치에서 검토한 후에 develop브랜치랑 합친다.
  5. 모든 기능이 완료되면 develop 브랜치를 master 브랜치와 develop 브랜치로 보낸다. master 브랜치에서 버전추가를 버전추가를 위해 태그를 하나 생성하고 배포를 한다.
  6. 배포를 했는데 미처 발견하지 못한 버그가 있을 경우 hotfixes 브랜치를 만들어 긴급 수정 후 태그를 생성하고 배포한다.
728x90

'Dev > Git' 카테고리의 다른 글

[Mac]GitHub push 오류(토큰 인증)  (1) 2021.08.21
728x90

공부하다가 우연히 저번에 다른분이 쓰신 블로그에 있는 애자일 소프트웨어 개발에 대해서 궁금해서 한번 정리 해볼려고한다.


💡애자일 소프트웨어 개발?

애자일 방법론은 소프트웨어 개발 방법에 있어서 아무런 계획이 없는 개발 방법과 계획이 지나치게 많은 개발 방법들 사이에서 타협점을 찾고자 하는 방법론이다.

 

💡애자일 소프트웨어 개발 선언 4가지

  • 공정과 도구 보다 개인과 상호 작용을 
  • 포괄적인 문서보다 작동하는 소프트웨어를
  • 계약 협상보다 고객과 협력
  • 계획을 따르기보다 변화에 대응하기를 가치 있게 여긴다.

💡애자일 선언 이면의 12가지 원칙

  • 우리의 최우선 순위는 가치 있는 소프트웨어를 일찍 그리고 지속적으로 전달해서 고객을 만족시키는 것이다.
  • 비록 개발의 후반부일지라도 요구사항 변경을 환영하라.애자일 프로세스들은 변화를 활용해 고객의 경쟁력에 도움이 되게한다.
  • 작동하는 소프트웨어를 자주 전달하라.2주에서 2개월 간격으로 하되 더 짧은 기간을 선호하라
  • 비즈니스 쪽의 사람들과 개발자들은 프로젝트 전체에 걸쳐 날마다 함께 일해야한다.
  • 동기가 부여된 개인들 중심으로 프로젝트를 구성하라. 그들이 필요로 하는 환경과 지원을 주고 그들이 일을 끝내리라 신뢰하라.
  • 개발팀으로 또 개발팀 내부에서 정보를 전하는 가장 효율적이고 효과적인 방법은 면대면 대화이다.
  • 작동하는 소프트웨어가 진척의 주된 척도이다.애자일 프로세스들은 지속 가능한 개발을 장려한다.
  • 스폰서, 개발자, 사용자는 일정한 속도를 계속 유지할 수 있어야한다.
  • 기술적 탁월성과 좋은 설계에 대한 지속적 관심이 기민함을 높인다.
  • 단순성이 - 안 하는 일의 양을 최대화하는 기술이 - 필수적이다.최고의 아키텍처, 요구사항, 설계는 자기 조직적인 팀에서 창발한다.
  • 팀은 정기적으로 어떻게 더 효과적이 될지 숙고하고, 이에 따라 팀의 행동을 조율하고 조정한다.

 

위 12가지 원칙 중 위 세가지는 소프트웨어를 일찍 그리고 지속적으로 전달하며, 요구사항 변경을 적극적으로 받아들이고, 짧은 주기마다 작동하는 소프트웨어를 제공하는 것이다. 이렇게 하기 위해서 반복(iteration) 백로그(backlog)와 같은 방법을 사용하여 관리한다.

 

  • 반복 주기(iteration): 모든 프로젝트 활동을 반복적으로 수행하여 작동하는 소프트웨어를 지속적으로 제공한다. 반복주기는 정해진 시간(Timedboxed)이 있기에 이 시기에 수행할 수 있은 일을  매 반복 주기에 우선순위를 두어 할당하고 조율하게 된다. 이를 통해 가장 중요한 요청사항에 집중해서 작업할 수 있는 환경을 만들 수 있다.
  • 백로그(backlog): 변하는 요구사항을 관리하는 아주 좋은 방법으로 반복주기에 포함되어 있지 않지만 개발예정된 피처목록이다.

이 내용을 읽으면서 느낀건 잘만들어도 고객들이 필요성을 못느낀다면 무조건 좋다고 말은 못할거 같다.개발자들이 조금 번거롭고 하더라도 고객들이 원하는 서비스를 제공하는 것이 더 맞는거 같다.진짜 개발은 끝이 없다.

 

 

 

대표 사진 출처:https://m.post.naver.com/viewer/postView.nhnvolumeNo=

27695616&memberNo=45977335&vType=VERTICAL

728x90

'Dev' 카테고리의 다른 글

[기술면접]네트워크(Network)  (0) 2022.01.11
[Database]트랜잭션 격리 수준(TransactionIsolationLevel)  (0) 2022.01.09
[AWS]EC2에 JDK 11설치하기  (0) 2021.10.25
세션 관리 전략  (0) 2021.10.21
REST API  (0) 2021.10.09
728x90

프로젝트를 진행하던 중 DTO를 개념만 알고 어디서 사용해야 적절한지에 대해서 고민해보지 않고 사용해서 정리를 해보려고 한다. 그래서 어디서 사용해야 적절한 것인지 알아보려 한다.


➡️DTO

DTO(Data Transfer Object)란 계층 간 데이터 교환을 위해 사용하는 객체이다.

 

MVC패턴으로 예를 들어보자

 

MVC 패턴은 애플리케이션을 개발할 때 그 구성요소를 Model과 View 및 Controller 등 세 가지 역할로 구분하는 디자인 패턴이다. 비즈니스 처리 로직(Model)과 UI영역(View)은 서로의 존재를 모르고 Controller가 중간에서 Model과 View를 연결하는 역할을 한다.

Controller는 View로부터 들어온 사용자 요청을 해석하여 Model을 업데이트하거나  Model로부터 데이터를 받아 View로 전달하는 작업을 수행한다.

MVC 패턴의 장점은 Model과 View를 분리함으로써 서로의 의존성을 낮추고 독립적인 개발을 가능하게 한다.

Controller는 View와 도메인 Model의 데이터를 주고받을 때 별도의 DTO 를 주로 사용한다.

왜냐하면 도메인 객체를 View에 직접 전달할 수 있지만, 민감한 도메인 비즈니스 기능이 노출될 수 있으며 Model과 View 사이에 의존성이 생기고 비즈니스 로직 등 민감한 정보가 외부에 노출되기 때문에 보안상의 문제가 생길 수 있기 때문이다. 

 

 

문제가 생길수 있는 예제를 한번 보자

public class User{

    public Long id;
    public String name;
    public String email;
    public String password; 

}
@GetMapping
public ResponseEntity<User> findeUser(@PathVariable long id) {
    User user = userService.findById(id);
    return ResponseEntity.ok(user);
   
}
  • 이런 식으로 컨트롤러가 클라이언트의 요청에 대한 응답으로 도메인 모델인 User를 넘겨주면 다음과 같은 문제점이 발생한다.
  • 도메인 모델의 모든 속성이 외부에 노출된다. 저 요청에서 사용하지 않는 데이터를 불필요하게 가지고 있는 것이다.
  • 모든 속성이 외부에 노출되기 때문에 보안상 좋지 않다.
  • View에서  Model의 메서드를 호출하거나 상태를 변경시킬 위험이 존재한다.
  • Model과 View가 상호 의존성이 높아져서 View에 요구사항이 있을 경우 Model에 영향을 끼칠 수 있다.

 

 

이제 DTO를 사용해보자 

public class UserDto {

    public long id;
    public String name;
    public String email;
	
    @Builder    
    public UserDto(long id,String name,String email){
         this.id = id;
         this.name = name;
         this.email = email;
  	}
}
@GetMapping
public ResponseEntity<UserDto> findUser(@PathVariable long id) {
    UserDto userDto = userService.findById(id);
    return ResponseEntity.ok(userDto);
}

이런 식으로 DTO를 사용하면 도메인 모델을 캡슐화하고, View에서 사용하는 데이터만 선택적으로 응답할 수 있다. DTO는 클라이언트 요청에 포함된 데이터를 담아 서버 측에 전달하고, 서버 측의 응답 데이터를 담아 클라이언트에 전달하는 계층 간 전달자 역할을 한다.

 

이제 DTO의 사용범위에 대해서 알아보자 

 

위 그림은 Layered Architecture이다. 유사한 관심사를 레이어로 나눠서 추상화하여 수직적으로 배열하는 아키택처이다. 하나의 계층은 주어진 역할을 수행하고, 인접한 다른 계층과 상호작용한다. 이런 식으로 시스템을 계층으로 나누면 시스템 전체를 수정하지 않고도 특정 계층을 수정 및 개선할 수 있어 재사용성과 유지보수에 유리하다.

 

한 로직을 예로 들어보자

View로부터 받아온 DTOController에서 도메인(Entity)으로 변환하고 서비스 계층에게 이를 전달하여 작업을 수행하고 서비스 계층은 Controller에게 도메인을 반환하고, Controller는 도메인을 DTO로 변환해 View로 응답을 보낸다.

여기서 보면 Controller가 도메인을 DTO로 변환을 한다. 그러나 잘 생각해보면 굳이 Controller에서 변환을 해야 하나 라는 생각이 든다.


서비스 계층이 요청으로 DTO를 받고 응답으로 DTO를 보내줘도 아무 문제가 없다. 그러면 어느 계층에서 사용하는 것이 자연스러운지 알아보자 

 

 

🪐Repository

Repository 계층은 Entity의 영속성을 관장하는 역할이라고 명시되어있다. 이로 인해, 표현 계층에서 사용할 도메인 계층을  DTO로 변환하는 작업을 책임지게 하는 것을 지양하자는 다수의 의견이 존재한다.

대부분은 Controller와 Service 계층에 위치시켰다.

 

🪐Service 

마틴 파울러의 말을 인용해보자면 Service계층이란 애플리케이션의 경계를 정의하고 비즈니스 로직 등 도메인을 캡슐화하는 역할이라고 정의한다. 즉, 도메인 모델을 표현 계층에서 사용하는 경우 결합도가 증가하여 도메인의 변경이 Controller의 변경을  촉발하는 유지보수의 문제로 이어질 수 있다.

이러한 관점에서 바라볼 때, 레이어 간 데이터 전달 목적으로 DTO를 엄격하게 고수한다면 변환 로직이 Service 레이어에서 정의되어야 한다는 의견이 존재했습니다. 요청에 대한 응답 역시 Service 레이어의 일부분이기 때문이다.

 

 

Service가 DTO를 사용하는 경우 Controller가 View로부터 받은 DTO를 Entity로 변환한 뒤, Service 레이어가 Entity를 전달받아 일련의 비즈니스 로직을 수행한다고 가정해보자.

  • 복잡한 애플리케이션의 경우 Controller가 View에서 전달받은 DTO만으로 Entity를 구성하기란 어렵다. Repository를 통해 여러 부수적인 정보들을 조회하여 Domain 객체를 구성할 수 있는 경우도 존재하기 때문이다.
  • Controller에서 DTO를 완벽하게 Domain 객체로 구성한 뒤 Service에 넘겨주려면, 복잡한 경우 Controller가 여러 Service(혹은 Repository)에 의존하게 된다. 

 

 

 

블로그와 깃 헙을 보면서 정답을 찾으려 했지만 대부분의 사람들이 명확한 답이 없고 상황에 따라 맞게 써야 한다고 한다. 

 

https://stackoverflow.com/questions/21554977/should-services-always-return-dtos-or-can-they-also-return-doma in-models

 

Should services always return DTOs, or can they also return domain models?

I'm (re)designing large-scale application, we use multi-layer architecture based on DDD. We have MVC with data layer (implementation of repositories), domain layer (definition of domain model and

stackoverflow.com

여기서 많은 개발자들이 서비스가 무조건 DTO를 반환해야 하는지 아니면 도메인 모델을 반환해도 되는지에 대한 질문이다.

 

여기서 나온 답을 정리해보자면 

  • 서비스 계층은 애플리케이션의 경계를 정의하고 도메인을 캡슐화한다.
  • DTO는 말 그대로 데이터 전달용 객체이고, communication에 사용된다면 의미가 있다. 대신 Presentation 레이어에서 도메인 모델을 사용하게 되면, 결합도가 증가해 코드 변경이 불가피할 수 있다
  • 도메인 모델과 똑같은 DTO를 만드는 것 같고 의미 없게 느껴진다 : DTO의 단점 중 하나이다. 대신 지금은 코드 중복으로만 생각되지만, 나중에 프로젝트가 확장되면 더 큰 의미를 발휘할 수 있다.

대규모 프로젝트에는 DTO를 사용하면 더 큰 의미를 발휘할 수 있다고 한다.

 

어느 블로그에서 본 걸로 인용하지만 DTO사용범위라는 고민보다는 도메인 모델 보호라는 관점에서 생각해보자고 한다. 나도 이 의견에 동의한다.그리고 Repository단위는 다들 공통적으로 지양한다.


📁정리

DTO를 공부하면서 많은 고민을 해봤다. 내가 아직 많이 부족해서 답을 결정짓기 어려운 내용인 거 같다. DTO는 상황에 맞게 사용하고 무한한 피드백으로 최적의 케이스를 찾아야 한다. 정리하자면 DTO를 남발하는 것은 좋지 않다. 상황에 따라 DTO가 필요 없을 수도 있다.

 

이렇게 의견이 다 다르고 정답이 없는 건 처음 느껴본다. 공부를 무한히 하고 많은 사람들과 토론을 해보는 것도 성장에 많은 도움이 될 것 같다.

728x90
728x90

📁DB Replication

https://jarvics.tistory.com/68

 

[MYSQL]효율적인 트래픽 분산을 위한 Master/Slave 동적 라우팅

사용자가 지속적으로 증가하면 많은 양의 트래픽이 발생한다. 그러므로 하나의 DB서버로 쓰기와 읽기 작업이 모두 진행된다면 쉽게 DB서버에 부하가 발생할 수 있다. 이 문제에 대한 해결책으로

jarvics.tistory.com

  • 데이터베이스 이중화 방식 중 하나로 Master DB + Slave DB로 구성한다.
  • Master DB에 데이터 변경이 감지되면 Master DB 로그를 기반으로 Slave DB에 복제한다.
  • Master DB에는 데이터 변경이 필요한 INSERT,DELETE,UPDATE 등의 쿼리가 사용하고, Slave DB에는 SELECT문이 사용한다.

 

Master DB 와 Slave DB를 나눠 구성한 다음 @Transaction의 readonly 속성이 true이면 Slave DB를 사용하고 false이면 Master DB를 사용한다.

 

우선 SpringBoot와 RDS(MYSQL)이 연동이 완료되었다는 가정하에 진행할 것이다.

 

우선 AWS 접속한 뒤 서비스에서 RDS로 들어가 본인이 생성한 DB인스턴스를 선택한다.

선택한 다음 작업을 클릭하여 읽기 전용 복제본 생성을 클릭한다.

클릭한 다음  퍼블릭 엑세스를 허용 해준다. 그러면 복제본 생성이 완료된다.

여기까지는 딱히 어려움이 없다.

 

다음은 스프링 프로젝트로 가서 설정을 해보자 

spring:
  datasource:
    url: jdbc:mysql://본인 RDS 앤드포인트URL:3306/사용할 데이터베이스 이름?useSSL=false&useUnicode=true&characterEncoding=utf8
    slave-list:
      - name: slave
        url: jdbc:mysql://본인 RDS 복제본 앤드포인트 URL:3306/사용할 데이터베이스 이름?useSSL=false&useUnicode=true&characterEncoding=utf8
    username: RDS설정 username
    password: RDS설정 password
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect
    database: mysql
    hibernate:
      ddl-auto: update
    generate-ddl: true

우선 RDS설정 정보를 적어 놓은 yml파일에 slave-list,name,url 을 추가해준다.

 

 

다음은 DataSource를 직접 설정해야하기 때문에 Spring을 실행할 때 DataSourceAutoConfiguration을 제외시켜준다.

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

이제 DB 설정파일을 가져올 DatabaseProperty클래스를 만든다.(yml에서 설정을 가져오는 클래스이다.)

@Getter
@Setter
@Component
@ConfigurationProperties("spring.datasource")
public class DatabaseProperty {
    private String url;
    private List<Slave>slaveList;
    private String username;
    private String password;
    private String driverClassName;
    
    @Getter
    @Setter
    public static class Slave {
        private String name;
        private String url;
    }
}

 

여러대의 Slave DB를 순서대로 로드밸런싱 하기위해  CircularList클래스를 만든다.

public class CircularList<T> {
    private List<T> list ;
    private Integer counter = 0;


    public CircularList(List<T> list) {
        this.list = list;
    }

    public T getOne(){
        if (counter + 1 >= list.size()){
            counter = -1;
        }

        return list.get(++counter);
    }
}

 

 

  • 여러개의 DataSource를 묶고 필요에 따라 분기처리를 위해 AbstractRoutingDataSource클래스를 사용해야 한다.
  • 여러대의 Slave DB를 순서대로 사용하기 위해 CircularList에 Slave DB 키를 추가 해준다.
  • determineCurrentLookup 메서드에서 현재 @Transactional(readOnly = true)일 경우 Slave DB로, 아닐 경우 Master DB의 DataSource의 키를 리턴하도록 설정해준다. 

 

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {

    private CircularList<String> dataSourceList;
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        dataSourceList = new CircularList<>(
                targetDataSources.keySet()
                        .stream()
                        .filter(key -> key.toString().contains("slave"))
                        .map(key -> key.toString())
                        .collect(toList()));
    }

    @Override
    protected Object determineCurrentLookupKey() {
        boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();

        if (isReadOnly){
            return dataSourceList.getOne();
        }else{
            return "master";
        }
    }
}

이제 최종적으로 DataSource, TransactionManager, EntityManager 설정을 해야한다.

우선 DataConfig 클래스를 생성한다.

 

@Configuration
public class DatabaseConfig {

    @Autowired
    private DatabaseProperty databaseProperty;
	
    //아래 routingDataSource에서 사용할 설정 메서드 
    public DataSource routingDataProperty(String url){
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setJdbcUrl(databaseProperty.getUrl());
        hikariDataSource.setDriverClassName(databaseProperty.getDriverClassName());
        hikariDataSource.setPassword(databaseProperty.getPassword());
        hikariDataSource.setUsername(databaseProperty.getUsername());

        return hikariDataSource;
    }


    @Bean
    public DataSource routingDataSource(){
        
        ReplicationRoutingDataSource replicationRoutingDataSource = new ReplicationRoutingDataSource();
        //#1
        DataSource master = routingDataProperty(databaseProperty.getUrl());
		
        Map<Object,Object> dataSourceMap = new LinkedHashMap<>();
        dataSourceMap.put("master",master);
		
        //#2
        databaseProperty.getSlaveList().forEach(slave -> {
            dataSourceMap.put(slave.getName() , routingDataProperty(slave.getUrl()));
        });
   		//#2
        replicationRoutingDataSource.setTargetDataSources(dataSourceMap);
        
        //#3
        replicationRoutingDataSource.setDefaultTargetDataSource(master);
        return replicationRoutingDataSource;
    }

    @Bean
    public DataSource dataSource() {
        return new LazyConnectionDataSourceProxy(routingDataSource());
    }

#routingDataSource 

  1. 우선 이전에 만들었던 ReplicationRoutingDataSource 클래스에 Master DB와 Slave DB를 추가해준다.
  2. replicationRoutingDataSource 의 replicationRoutingDataSourceNameList 세팅한다.(Slave Key 이름 리스트 세팅)
  3. 디폴트는 Master로 설정 

#dataSource

  • LazyConnectionDataSourceProxy는 실제 쿼리가 실행될 때 Connection을 가져온다.
  • LazyConnectionDataSourceProxy는 실질적인 쿼리 실행 여부와 상관없이 트랜잭션이 걸리면 무조건 Connection객체를 확보하는 Spring의 단점을 보완하며 트랜잭션 시작시에 Connection Proxy객체를 리턴하고 실제로 쿼리가 발생할 때 데이터 소스에서 getConnection()을 호출하는 역할을 한다.
  • TransactionSynchronizationManager가 현재 트랜잭션을 상태를 읽어올 수 있지만 트랜잭션 동기화 시점과 Connection이 연결되는 시점이 다르기 때문에 LazyConnectionDataSourceProxy를 사용하여 Connection객체를 가져온다.

이제 기본설정은 어느정도 마무리 되었고 JPA에서 사용할 EntityManager과 TransactionManager 설정을 해준다.

@Configuration

public class DatabaseConfig {
		
       
   		.
		.
		.
        
        
	@Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan("엔티티가 위치한 패키지 경로" ex)com.example);
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);


        return entityManagerFactoryBean;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){
        JpaTransactionManager tm = new JpaTransactionManager();
        tm.setEntityManagerFactory(entityManagerFactory);
        return tm;
    }
}

이제 JPA 설정까지 마무리 했다.

테스트를 한번 해보자


Test

 

@Entity
@Table(name = "member")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;


    @Builder
    public Product(String name) {
        this.na,e = name;
    }
}
public interface MemberRepository extends JpaRepository<Member, Long> {}
@RestController
@RequestMapping("/api/members")
public class MemberController {

    @Autowired
    private MemberService memberService;

    @GetMapping
    public ResponseEntity<?> getMembers() {
        List<Member> memberList = memberService.getMembers();
        return new ResponseEntity<>(memberList, HttpStatus.OK);
    }
    @GetMapping("/masterDB")
    public ResponseEntity<?> getMembersFromMasterDB() {
        List<Member> memberList = memberService.getMembersMaster();
        return new ResponseEntity<>(memberList, HttpStatus.OK);
    }
}
@Service
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;

    @Transactional(readOnly = true)
    public List<Member> getMembers() {
        return memberRepository.findAll();
    }
    @Transactional
    public List<Member> getMembersMaster() {
        return memberRepository.findAll();
    }
}

Master/Slave DB에 쿼리가 날아가는 것을 확인하기 위해 yml 파일에 설정을 추가해준다.

 

logging:
  level:
    org.springframework.jdbc.datasource.SimpleDriverDataSource: DEBUG
    org.hibernate.SQL: DEBUG

 

이제 DB에 member Table을 생성할것이다. 그러나 원래 JPA는 자동으로 Table을 생성할 수 있지만 처음에 DataSourceAutoConfiguration를 제외시켰기 때문에 직접 설정해주어야한다.

 

SQL

CREATE TABLE `member` (
  `id` long NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

insert into `member` (name) values ('name1'), 
('name2'), ('name3');

서버를 실행 후 /api/members 에 GET요청을 하면 아래와 같은 결과값이 나온다.

[
    {
        "id": 1,
        "name": "name1",
    },
    {
        "id": 2,
        "name": "name2",
    },
    {
        "id": 3,
        "name": "name3",
    }
]

 

SimpleDriverDataSource의 로그를 확인해보면

Creating new JDBC Driver Connection to [jdbc:mysql://campshop-slave.concrp2jli...

이렇게 Slave DB를 사용합니다.

 

 

/api/member/master에 요청을 보내면

Creating new JDBC Driver Connection to [jdbc:mysql://campshop-db.concrp...

이렇게 Master DB를 사용합니다.

 

이런식으로 대용량 트래픽으로 인해 생기는 데이터베이스 문제를 효율적으로 분산시키며 해결할 수 있다.

 

 

 


REFERENCE

728x90

[Server] CI/CD

ryudjae
|2021. 11. 8. 02:52
728x90

 

➡️CI (Continious Integration)

  • CI는 Continious Integration의 약자이며, 지속적 통합이라는 의미이다.
  • 지속적 통합이란, 애플리케이션의 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트 되어 공유 레포지토리에 통합하는 것을 의미한다.
  • CI는 지속적으로 서비스해야 하는 애플리케이션은 기능 추가 시마다 커밋을 날려 저장소에 버전을 업데이트 해야한다. 여러명의 사람이 한팀으로 작업을 할 경우 많은 커밋들이 쌓이게 된다. 그럴 때마다 , 기능 별로 빌드/테스트/병합을 한다면 너무 번거로운 작업이 된다.이러한 상황에서 자동화된 빌드 & 테스트는 소스코드의 충돌 등을 방어하는 이익을 제공할 수 있다.
  • CI는 MSA환경에서는 대부분 소규모 기능 단위로 빠르게 개발 & 적용을 반복하는 방법론이 적용되기 때문에 기능 추가가 빈번하게 발생하게 된다. 이러한 상황에서 CI적용은 기능 충돌 방지등의 이익을 제공할 수 있다.
  • 그래서 CI목표는 버그를 신속하게 찾아 해결하고, 소프트웨어 품질을 개선하고, 새로운 업데이트의 검증 및 릴리즈의 시간을 단축시키는 것에 있다.

 

➡️CD (Continuous Delivery & Continuous Deployment)

  • CD는 Continuous Delivery & Continuous Deployment 두 용어의 약자이다. 해석을 하자면 지속적인 서비스 제공 혹은 지속적인 배포란 의미이다.
  • Continuous Delivery는 공유 리포지토리로 자동으로 릴리즈 하는 것이고, Continuous Deployment는 production레벨까지 자동으로 deploy 하는 것을 의미한다.
  • CD는 개발자의 변경 사항이 리포지토리를 넘어, 고객의 프로덕션 환경까지 릴리즈 되는 것을 의미한다.
  • CD는 서비스 이용자가 최대한 빠른 시간 내에 최신 버전의 Production을 제공받을 필요가 있습니다. 그렇다는것은 소프트웨어가 언제든지 신뢰 가능한 수준의 버전을 유지할 수 있도록 지원하는 것이 CD라고 한다.

 

대표적인 CI/CD툴 

 

728x90

'Dev > 인프라' 카테고리의 다른 글

AWS Route 53  (5) 2024.09.12
Docker  (1) 2024.09.10
[Server] 로드 밸런싱(Load balancing)  (0) 2021.11.05
[AWS] EC2 인스턴스 구축하기  (0) 2021.10.24
[Server]운영 서버에 대한 정리  (0) 2021.10.21