📌 공부 계기

회사 프로젝트의 변수 이름에 @가 붙어있는 케이스를 발견 

변수에 @사인이 들어가는 걸 처음봐서 찾아보니 C#에서는 여러 기능으로 @을 사용하고 있어 정리해본다. 

 


사용 1 ) 문자 그래도 인식 방법

대입하고자 하는 문자열 앞에 @을 붙여주어 사용한다. 

Escape문자를 무시하고 문자를 그대로 문자열로 인식한다

// 기본
string str = "C:\\Test\\Test.txt";

// @ 사용
string str = @"C:\Test\Test.txt";

 

또한 문자열을 그대로 인식하기 때문에 Enter도 인식하여 \n Escape 문자 없이 복수행으로 작성된 문자열을 그대로 인식할 수 있다. 

// 기본 
string str = "기본 문자열은 Enter시 \\n 을 사용하여야 합니다. \n 줄바꿈이 되었습니다.";

// @ 사용
string str = @"@을 사용한 문자열은 Enter시 \n 을 사용하지 않습니다.
줄바꿈이 되었습니다.";

 


사용 2 ) C# 키워드를 변수명으로 사용

C# 키워드는 예약어로 원래 변수명으로 사용할 수 없지만 앞에 @을 붙여주게 되면 변수명으로 사용할 수 있다. 

선언할 때도 대입하여 사용할때도 @을 붙여 사용해야 한다. 

예약어를 변수명으로 사용하는 것은 기피되어야 하나,
피치못하게 사용해야하는 케이스 등에 사용한다. ( ex. ASP.NET MVC의 Html Helper )

string @object = "객체";

@object = "문자열" + @object;
Console.WriteLine(@object);
// 문자열객체

 

 

 


🧐 My Case

우리회사에서 발견한 변수는 코드비하인드가 아닌 aspx 파일에서 asp태그의 id를 fixed로 사용했는데 

이게 C#에서 fixed키워드는 "가비지 콜렉터에서 이동 가능한 변수를 재배치 할 수 없도록 포인터를 고정시키는 역할"을 하는 키워드이다.

따라서 키워드 변수를 사용할 수 밖에 없는 상황.

근데 생각해보면 이런 상황을 피하기 위해 태그 쪽 id를 변경하여 키워드 변수를 사용하지 않도록 하는게  더 좋을 거 같긴하다.

해당 변수 사용된 걸 전체 파악해서 나중에 건의해보는 걸로..


참고 자료 :

 

C#에서 @을 사용할 때 - C# 프로그래밍 배우기 (Learn C# Programming)

@ 심벌을 사용법 1 @ 심벌을 문자열 앞에 사용하면, 해당 문자열 안의 Escape 문자를 무시하고 문자 그대로 인식하도록 한다. 예를 들어, 파일 패스를 지정할 때, Backslash를 한번 지정하면 이는 Escape

www.csharpstudy.com

 

c# 에서 checked, fixed, lock, params

1. checked / unchecked checked는 키워드는 정수 형식 산술 연산 및 변환에 대한 오버플로 검사를 명시적으로 사용하도록 설정하는 데 사용됩니다. int ten = 10; int i2 = 2147483647 + ten; 상수 값만 포함된 식이

moonpmj.tistory.com

 

 

 

📌 사용 계기

데이터 검색 성능 개선을 해보는 프로젝트에서 검색 상태에 따라 다른 JPQL문을 생성할 필요가 있어 QueryDSL을 적용하게 되었다. 

 


📍QueryDSL이란?

JPQL문을 문자열이 아닌 자바 코드로 작성할 수 있도록 빌더 역할을 하는 오픈 소스

 


📍QueryDSL 특징

  • 쿼리를 코드로 작성해 컴파일 시 오류 발견이 가능하다.
  • 코드 기반에 단순하여 사용이 쉽고 가독성이 좋음
  • 자동 완성 등 IDE의 도움 가능
  • 동적 쿼리 구현 가능

 


📍적용 방법

  • build.gradle
buildscript {						// gradle로 task를 수행할 때에 사용되는 설정
	ext {							     // build.gradle에서 사용하는 전역 변수 설정
		queryDslVersion = "5.0.0"	// 변수 queryDslVersion에 5.0.0 대입
	}
}

plugins {							// gradle task의 집합
	//....//
	id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'	// querydsl plugin 적용
}

dependencies {
	//....//

	// querydsl 의존성 추가
  // buildscript에서 적용한 변수 queryDslVersion이 여기에 적용
	implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"	
	implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

// 변수 선언, 그 변수에 폴더 경로 저장
def querydslDir = "$buildDir/generated/querydsl"			

querydsl {															
	jpa = true														// JPA 사용여부 설정
	querydslSourcesDir = querydslDir		  // 사용할 경로 설정
}

sourceSets {														// build시 사용할 sourceSet 추가
	main.java.srcDir querydslDir
}

compileQuerydsl {												// queryDSL 컴파일시 사용할 옵션 설정
	options.annotationProcessorPath = configurations.querydsl
}

configurations {												// build 옵션
	compileOnly {
		extendsFrom annotationProcessor
	}
   // querydsl이 compileClasspath를 상속하도록 설정
	querydsl.extendsFrom compileClasspath	 
}
  • config
@Configuration
public class QuerydslConfig {

    @PersistenceContext
    // @PersistenceContext로 주입받은 Entity Manager은 Proxy로 감싸진다.
    // EntityManager 호출시 마다 Proxy를 통해 EntityManager을 생성하여 Thread-Safe를 보장
    private EntityManager entityManager;

    @Bean
    // JPAQuery를 생성해주는 factory클래스
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}
  • Q-Class

QueryDSL에서는 Entity로 설정된 클래스에 Q모델의 쿼리 타입 클래스를 미리 생성하고 메타데이터로 사용하여 쿼리를 메소드 기반으로 작성한다.

 

생성하는 방법은 Tool Window Bar (사이드에 보이는 메뉴명)에서 Gradle > other > compileJava 클릭

생성된 것을 확인

 

 


 

📍 QueryDSL 구현 방법 (총 3가지)

  • 방법 1 )  QuerydslRepositorySupport를 상속 받아 사용하는 방법
    • 구현코드
      • JpaRepository를 상속 받는 ProductRepository 인터페이스 하나
        import com.example.showmethemany.domain.Products;
        import org.springframework.data.jpa.repository.JpaRepository;
        
        public interface ProductRepository extends JpaRepository<Products, Long> {
        }
      • QuerydslRepositorySupport를 상속하고 Super생성자에 Entity를 지정해서 넣은 클래스 하나(ProductRepositorySupport)
        import com.example.showmethemany.domain.Products;
        import com.querydsl.jpa.impl.JPAQueryFactory;
        import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
        import org.springframework.stereotype.Repository;
        
        import static com.example.showmethemany.domain.QProducts.products;
        
        @Repository
        public class ProductRepositorySupport extends QuerydslRepositorySupport {
        
            private final JPAQueryFactory queryFactory;
            public ProductRepositorySupport(JPAQueryFactory queryFactory) {
                super(Products.class);
                this.queryFactory = queryFactory;
            }
        
            public Products findByProductId (Long productId) {
                return queryFactory.select(products)
                        .from(products)
                        .where(products.id.eq(productId))
                        .fetchOne();
            }
        }
    • 테스트코드
      import com.example.showmethemany.domain.Products;
      import org.junit.jupiter.api.DisplayName;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import static org.junit.jupiter.api.Assertions.*;
      
      @SpringBootTest
      class ProductRepositorySupportTest {
      
          @Autowired
          ProductRepository productRepository;
      
          @Autowired
          ProductRepositorySupport productRepositorySupport;
      
          @Test
          @DisplayName("QueryDSL Support 상속 구현 방식 테스트")
          void findByProductId() {
              //given
              Long productId = 1L;
      
              //when
              Products products = productRepositorySupport.findByProductId(productId);
      
              //then
              assertEquals(26910, products.getPrice());
          }
      }
    • 결과
      그러나 이 구현 방법의 문제점은 
      QuerydslRepositorySupport(코드상 ProductRepositorySupport)와 JpaRepository(코드상 ProductRepository)가 기능을 나눠 가져 항상 2개를 의존성으로 받아야 하는 문제가 있다
     
  • 방법 2 ) CustomRepository 상속과 구현이 필요한 방법
    • 구현코드
      • CustomRepository 인터페이스 하나
        import com.example.showmethemany.domain.Product
        
        public interface ProductRepositoryCustom {
            Products findByProductId (Long productId);
        }
      • CustomRepository 의 구현체 하나
        import com.example.showmethemany.domain.Products;
        import com.querydsl.jpa.impl.JPAQueryFactory;
        import javax.persistence.EntityManager;
        import static com.example.showmethemany.domain.QProducts.products;
        
        public class ProductRepositoryImpl implements ProductRepositoryCustom {
        
            private final JPAQueryFactory queryFactory;
        
            public ProductRepositoryImpl(EntityManager em) {
                this.queryFactory = new JPAQueryFactory(em);
            }
        
            @Override
            public Products findByProductId(Long productId) {
                return queryFactory.select(products)
                        .from(products)
                        .where(products.id.eq(productId))
                        .fetchOne();
            }
        }
      • JpaRepository 에 CustomRepository를 상속함으로써 CustomRepositoryImpl도 사용 가능
        import com.example.showmethemany.domain.Products;
        import org.springframework.data.jpa.repository.JpaRepository;
        
        public interface ProductRepository extends JpaRepository<Products, Long>, ProductRepositoryCustom {
        }
    • 테스트코드
      @SpringBootTest
      class ProductRepositoryTest {
      
          @Autowired
          ProductRepository productRepository;
      
          @Test
          @DisplayName("QueryDSL CustomRepository 상속 방식 테스트")
          void findByProductId() {
              //given
              Long productId = 1L;
      
              //when
              Products products = productRepository.findByProductId(productId);
      
              //then
              assertEquals(26910, products.getPrice());
          }
      }
    • 결과
      그러나 이 구현 방법의 문제점은 
      하나의 Repository당 interface하나와 Impl하나 총 2개를 더 만들어줘야 한다. (총 3개)
     
  • 방법 3 ) 상속과 구현 없이 가능한 방법
    • 구현코드
      • JPAQueryFactory를 주입 받은 Repository class 하나
        JPAQueryFactory만 주입 받으면 QueryDSL을 사용할 수 있고 특정 Entity 지정하지 않아도 여러 Entity를 대상으로 사용할 수 있다.
        import com.example.showmethemany.domain.Products;
        import com.querydsl.jpa.impl.JPAQueryFactory;
        import org.springframework.stereotype.Repository;
        import static com.example.showmethemany.domain.QProducts.products;
        
        @Repository
        public class ProductRepository {
        
            private final JPAQueryFactory queryFactory;
        
            public ProductRepository(JPAQueryFactory queryFactory) {
                this.queryFactory = queryFactory;
            }
        
            public Products findByProductId (Long productId) {
                return queryFactory.select(products)
                        .from(products)
                        .where(products.id.eq(productId))
                        .fetchOne();
            }
        }
    • 테스트코드
      @SpringBootTest
      class ProductRepositoryTest {
      
          @Autowired
          private ProductRepository productRepository;
      
          @Test
          @DisplayName("QueryDSL 상속/구현 없는 방식 테스트")
          void findById() {
              //given
              Long productId = 1L;
      
              //when
              Products products = productRepository.findByProductId(productId);
      
              //then
              assertEquals(26910, products.getPrice());
          }
      }
    • 결과
      이 구현 방법은 다른 경우보다 부담이 덜된다는 장점은 있지만 기본 JpaRepository와는 별개가 되기 때문에 기존 Repository의 메서드를 참조할 수 없게 된다. QueryDSL만 사용하게 되면 JPA로 간단히 사용했던 메소드를 구현해 줘야 한다는 단점이 있다. 그래서 프로젝트에는 QueryDSL과 Spring Data JPA를 같이 쓰는 방향으로 진행했다. 

📌 김영한 님의 "스프링 핵심 원리 - 기본편" 강의 듣고 정리

목차

     

    Section 1 . 객체 지향 설계와 스프링

    스프링이란? 

     

    ☘️ 스프링 생태계

    필수!

    • 스프링 프레임워크
    • 스프링 부트 : 스프링 기술들을 편하게 사용할 수 있게 도움을 주는 기술

    👆 선택

    • 스프링 데이터 : 데이터베이스를 사용하는 데 도움을 주는 기술 (가장 많이 쓰는게 Spring Data JPA)
    • 스프링 세션 : 세션 기능을 편리하게 사용할 수 있도록 도움을 주는 기술
    • 스프링 시큐리티 : 보안
    • 스프링 Rest Docs : API 문서와 테스트를 엮어 편리하게 도움을 주는 기술
    • 스프링 배치 : 배치 처리에 특화된 기술
    • 스프링 클라우드 : 클라우드에 특화된 기술
    • 등등 

     

    ☘️ 스프링 프레임 워크

    • 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타 (강의의 초점!!)
    • 웹 기술: 스프링 MVC, 스프링 WebFlux
    • 데이터 접근 기술: 트랜잭션, JDBC, ORM 기원, XML 지원
    • 기술 통합: 캐시, 이메일, 원격접근, 스케쥴링
    • 테스트: 스프링 기반 테스트 지원
    • 언어: 코틀린, 그루비
    • 최근에는 스프링 부트를 통해서 스프링 프레임원크의 기술들을 편리하게 사용

     

    ☘️ 스프링 부트

    • 스프링을 편리하게 사용할 수 있도록 지원, 최근에는 기본으로 사용
    • 단독으로 실행할 수 있는 스프링 애플리케이션을 쉽게 생성
    • Tomcat 같은 웹 서버를 내장해서 별도의 웹 서버를 설치하지 않아도 됨
    • 손쉬운 빌드 구성을 위한 starter 종속성 제공
    • 스프링과 3rd party(외부) 라이브러리 자동 구성, 의존성 버전 관리
    • 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공 (모니터링 제공)
    • 관례에 의한 간결한 설정
    • 스프링 부트는 스프링과 별도로 사용가능한 것이 아니다! 스프링 프레임워크 위에서 편리하게 기능을 사용할 수 있도록 돕는다!

     

    ☘️ 스프링 단어?

    스프링이라는 단어는 문맥에 따라 다르게 사용된다.

    • 스프링 DI 컨테이너 기술
    • 스프링 프레임워크
    • 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계 

     

    ☘️ 스프링 왜 만들었나요?

    핵심 개념: 이 기술을 왜 만들었는가? 이  기술의 핵심 컨셉은? (로드존슨이랑 유겐 힐러가 왜 만들었을까!?!?!)

     

         ❓웹 애플리케이션 만들고, DB 접근 편리하게 해주는 기술?

         ❓전자정부 프레임워크?

         ❓웹 서버도 자동으로 띄워주고?

         ❓클라우드, 마이크로 서비스?

     

    인걸까?? ❌ 이건 다 결과물일 뿐이다

     

     * 스프링의 진짜 핵심

    • 스프링은 자바 언어 기반의 프레임워크
    • 자바 언어의 가장 큰 특징 - 객체 지향 언어
    • 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크
    • 스프링은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와주는 프레임워크

    사람들이 스프링 등장에 열광했던 건 스프링 이전의 EJB는 객체 지향의 장점을 망치는 기술이었기 때문에 이걸 살릴 수 있었던 기술에 열광했던 거다!! 

     

     


    좋은 객체 지향 프로그래밍이란?

     

    ☘️ 객체 지향 특징

    • 추상화 
    • 캡슐화
    • 상속
    • 다형성

     

    ☘️ 객체 지향 프로그래밍

    • 객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러개의 독립된 단위,
      "객체"들의 모임으로 파악하고자 하는 것이다. 각자의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력)
      • 혼자 있는 객체는 없다.
      • 객체끼리 뿐만 아니라 수많은 객체 클라이언트와 객체 서버는 서로 협력 관계를 가진다. (요청과 응답하면서)
      • 서버끼리도, 시스템끼리도! 
    • 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다. 

    유연하고, 변경이 용이하다? 

    객체 단위로 개발하기 때문에

    • 레고 블럭 조립, 키보드 마우스 교체, 컴퓨터 부품 교체 처럼
    • 컴포넌트를 쉽고 유연하게 변경하면서 개발할 수 있는 방법

    이런 궁극의 유연함을 가질 수 있게 하는 객체 지향의 핵심! 다형성(Polymorphism) 이다. 

     

     

    ☘️ 다형성(Polymorphism) 

    자동차라는 역할이 존재하고 이 자동차 역할에 그 어떤 종류에 자동차가 구현(K3든, 아반떼든, 테슬라든) 되어도 운전자는 자동차를 운전할 수 있다. 운전자는 자동차 역할이라는 인터페이스에 의존하고 있기 때문에 구현이 어떤게 와도 상관없다.

    이는 운전자를 위한 것이다.

    자동차 내부 동작 방식이 바뀌어도 운전자에게는 영향이 없기 때문에 쓰던 방식 그대로 쓸 수 있다. 운전자는 새로 뭔가를 배울 필요가 없다. 또한 구현체를 바꿔버려도 운전이 그대로 가능하니까 자동차 역할만 지키면 새로운 구현체도 들어올 수 있어 확장성이 생긴다.

    이게 가능한 것이 역할과 구현을 나눠놨기 때문이다.  (연극에서 하나의 역할을 여러사람이 할 수 있는 것도)

     

     

    ☘️ 역할과 구현을 분리

    • 역할과 구현으로 구분하면 세상이 단순해지고, 유연해지며 변경도 편리
    • 장점
      • 클라이언트는 대상의 역할(인터페이스)만 의존
      • 클라이언트는 구현 내부 몰라도 된다. 변경도 몰라도 되며, 심지어 구현 대상 자체가 바뀌어도 영향 X

    프로그래밍 언어에서도 이러한 장점을 받아들이게 된다.

     

     

    ☘️ 자바 언어에서는 이걸 어떻게 받아들였을 까?

    자바 언어의 다형성을 활용한 방식은 객체 설계시 역할과 구현을 명확하게 분리했다.

    객체 설계시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만드는 방식으로 차용

    • 역할 = 인터페이스
    • 구현 = 인터페이스를 구현한 클래스,  구현 객체

    역할 > 구현. 역할이 더 중요하다

     

    오버라이딩 된 메서드가 실행될 때 다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경 할 수 있다.

    상속 관계에서도 다형성, 오버라이딩 적용 가능

     

     

    ☘️ 다형성의 본질

    • 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 할 수 있다. 
    • 다형성의 본질을 이해하려면 협력이라는 객체사이의 관계에서 시작해야한다. 
    • 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다. 

     

    ☘️ 역할과 구현을 분리 : 정리

    • 역할과 구현이라는 컨셉을 다형성을 통해 객체화 할 수 있다.
    • 유연하고 변경이 용이
    • 확장 가능
    • 클라이언트에 영향을 주지 않는 변경이 가능

    따라서, 인터페이스를 안정적으로 잘 설계하는 것이 중요하다. 

     

     

    ☘️ 하지만 한계도 있다

    역할(인터페이스)이 변하면 클라이언트, 서버 모두 변경이 발생한다. 그래서 더더욱 안정적인 인터페이스가 필요하다.  

     

     

    ☘️ 스프링과 객체 지향

    객체지향의 꽃은 다형성

    스프링은 이런 다형성을 극대화해서 이용이 가능하게 한다.

    스프링에서 말하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원해주는 역할을 해준다!

    그래서 결론! 스프링을 사용하면 블럭 조립하든 구현을 편리하게 변경할 수 있다!

     

    다형성이 스프링에 굉장히 중요한데 여기서 한가지가 더 있어야 완전히 스프링을 설명할 수 있다.

    그게 바로 좋은 객체 지향 설계의 5가지 원칙, 소위 SOLID 라고 불리는 원칙이다.


    좋은 객체 지향 설계의 5가지 원칙 (SOLID)

     

    ☘️ SRP (단일 책임의 원칙 / Single reponsibility principle)

    한 클래스는 하나의 책임만 가져야 한다는 원칙. 하지만 하나의 책임이라는 게 모호하고 문맥과 상황따라 다를 수 있는 거라 중요한건 변경을 기준으로 한다. 변경의 이유를 하나만 가질 것 변경의 이유가 여러가지가 생겼다면 한가지 이상의 책임을 맡고 있는 것이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.

     

    ☘️ OCP (개방-폐쇄 원칙 / Open-Closed principle)

    소프트웨어 요소는 확장에는 열려있고, 변경에는 닫혀있어야 한다. 

    다형성을 활용해 이 원칙을 지킬수가 있다. 인터페이스를 구현한 새로운 클래스를 만들어 새로운 기능을 구현하면 확장에는 열려있다. 그리고 이 변화가 외부의 기존 코드에 변경을 주지 않는다. 변경에는 닫혀있는 것이다. 

     

    그런데, 개발 폐쇄 원칙은 문제점이 있다. 

    만약 MemberRepository m = new MemoryMemberRepository(); 를 

    MemberRepository m = new JdbcMemberRepository();로 구현 객체를 변경할 경우

    클라이언트가 코드에서 변경을 해야한다. 다형성을 사용했으나 OCP 원칙을 지킬 수 없는데 이 문제를 해결하기 위해 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자가 필요하다. 

     

    이 역할을 스프링의 컨테이너가 해준다! 이 원칙을 지키기위해 IoC(제어의 역전)도 DI(의존성 주입)도 필요한 것!

     

    ☘️ LSP (리스코프 치환 원칙 / Liskov Substitution principle)

    프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. 

    이것은 단순히 코드에 상위 타입이 하위타입의 인스턴스로 교체를 성공하여 컴파일이 성공하는 걸 의미하지 않는다.  

    상위 타입의 객체를 하위 타입의 객체로 치환했다고 하더라도 상위 타입으로 동작하던 기존 프로그램은 정상적으로 동작해야하는 원칙이다. 상위의 엑셀 기능이 하위 타입으로 교체했을 때 후진 기능이 되버리면 리스코프 치환 원칙에 위배된 것이다. 하위 클래스는 인터페이스 규약을 다 지켜야한다. 그래서 다형성을 지원하기 위해 필요한 원칙으로 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다. 

     

    ☘️ ISP (인터페이스 분리 원칙 / Interface segregation principle)

    특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 

     

    자동차 인터페이스 > 운전 인터페이스, 정비 인터페이스 로 분리

    사용자 클라이언트 > 운전자 클라이언트, 정비사 클라이언트로 분리

    이런식으로 분리하면 정비 인터페이스를 변경해도 정비사 클라이언트만 영향을 주지 운전자 클라이언트는 영향을 주지않는다. 기능을 적당한 크기로 자르는 것이 중요. 인터페이스(역할)이 명확해지고, 하나의 큰 역할이 아닌 보다 작은 역할들로 잘라낸 것이라 대체 가능성이 높아진다.

     

    ☘️ DIP (의존관계 역전 원칙 / Dependency inversion principle)

    구체화에 의존하지 말고 추상화에 의존해라! = 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

     

    SOLID에서 특히 중요한 원칙이 OCP와 DIP

    앞서 이야기한 역할에 의존해야하는 것과 같은 이야기! 클라이언트가 인터페이스를 의존해야 유연하게 구현체를 바꿀 수 있기 때문이다. 

     

    만약 MemberRepository m = new MemoryMemberRepository(); 를

    MemberRepository m = new JdbcMemberRepository();로 구현 객체를 변경할 경우

     

    이 예시를 보면 인터페이스를 의존하면서도 구현 클래스에도 결국 같이 의존하고 있어 결국 구현체 변경시 코드까지 변경해야 하는 것이다. (DIP 위반)

    그럼 어떻게 하라는 거지?

     

    ☘️ 정리

    객체 지향의 핵심은 다형성이지만 다형성만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
    결국 구현 객체 변경시 클라이언트 코드 또한 변경해야하기 때문에 OCP, DIP를 지킬수 없다.

    이걸 지키기 위해 무언가가 필요한데 그걸 지키기 위해 탄생한게 스프링이다!

     


    객체 지향 설계와 스프링

     

    ☘️ 스프링 이야기가 객체 지향에서 나오는 이유?

    스프링은 아래 기술로 다형성 + OCP, DIP를 가능하게 지원한다

    • DI(Dependency Injection) : 의존관계, 의존성 주입
    • DI 컨테이너 제공

    이 기술들로 클라이언트 코드 변경 없이 기능을 확장이 가능하게 하고 부품 교체하듯 대체도 가능하다.

     

    ☘️ 정리

    • 모든 설계에 역할구현을 분리 (예시였던 자동차와 연극의 역을 생각해!)
    • 애플리케이션 설계도 인터페이스(역할)을 만들어두고 언제든 유연하게 변경할 수 있도록 만드는 게 좋은 객체 지향 설계다.  (> 이걸 가능하게 하는 것이 스프링 컨테이너)

    이상적으로는 모든 설계에 인터페이스 부여 하지만 이걸 도입하려면 추상화라는 비용이 발생한다.

    따라서 기능을 확장할 가능성이 없다면 구체 클래스를 직접 사용하고, 후에 리팩토링을 통해 인터페이스를 도입하는 것도 방

    'Programming > Spring' 카테고리의 다른 글

    QueryDSL 적용 방법  (0) 2023.04.01
    WebRTC 시그널링 서버 (Spring Boot로 구현)  (0) 2023.01.12
    Query DSL  (0) 2022.12.15
    스프링 부트(Spring Boot) 다운 그레이드  (0) 2022.12.07
    ORM / JPA / Hibernate / Spring Data JPA  (0) 2022.12.05

     

    🤔 더미 데이터가 왜 필요해?

    상품 데이터는 쿠팡에서 크롤링해 왔지만 실서비스를 목적으로 하지 않는 우리 프로젝트에 유저 경우에는 더미 데이터로 진행하기로 했다.

    랜덤으로 데이터를 생성해주는 서비스를 이용하여 집어 넣을 예정이었는데, 언어가 한글인 게 없어서 직접 코딩하기로!

     

    간단히 파이썬으로 코딩

     


    😃 더미 데이터 생성 코드

    🪪 랜덤 이름

    # 이름 생성 함수들
    def firstName():
        first = ["김", "이", "박", "최", "정", "강", "조", "윤", "장", "임", "한", "오", "서", "신", "권", "황", "안",
                 "송", "류", "전", "홍", "고", "문", "양", "손", "배", "조", "백", "허", "유", "남", "심", "노", "정",
                 "하", "곽", "성", "차", "주", "우", "구", "신", "임", "나", "전", "민", "유", "진", "지", "엄", "채",
                 "원", "천", "방", "공", "강", "현", "함", "변", "염", "양", "변", "여", "추", "노", "도", "소", "신",
                 "석", "선", "설", "마", "길", "주", "연", "방", "위", "표", "명", "기", "반", "왕", "금", "옥", "육",
                 "인", "맹", "제", "모", "장", "남", "탁", "국", "여", "진", "어", "은", "편", "구", "용"]
    
        return random.choice(first)
    
    def LastName():
        last =  ["가", "강", "건", "경", "고", "관", "광", "구", "규", "근", "기", "길", "나", "남", "노", "누", "다",
                "단", "달", "담", "대", "덕", "도", "동", "두", "라", "래", "로", "루", "리", "마", "만", "명", "무", "문", "미", "민", "바", "박",
                "백", "범", "별", "병", "보", "빛", "사", "산", "상", "새", "서", "석", "선", "설", "섭", "성", "세", "소", "솔", "수", "숙", "순",
                "숭", "슬", "승", "시", "신", "아", "안", "애", "엄", "여", "연", "영", "예", "오", "옥", "완", "요", "용", "우", "원", "월", "위",
                "유", "윤", "율", "으", "은", "의", "이", "익", "인", "일", "잎", "자", "잔", "장", "재", "전", "정", "제", "조", "종", "주", "준",
                "중", "지", "진", "찬", "창", "채", "천", "철", "초", "춘", "충", "치", "탐", "태", "택", "판", "하", "한", "해", "혁", "현", "형",
                "혜", "호", "홍", "화", "환", "회", "효", "훈", "휘", "희", "운", "모", "배", "부", "림", "봉", "혼", "황", "량", "린", "을", "비",
                "솜", "공", "면", "탁", "온", "디", "항", "후", "려", "균", "묵", "송", "욱", "휴", "언", "령", "섬", "들", "견", "추", "걸", "삼",
                "열", "웅", "분", "변", "양", "출", "타", "흥", "겸", "곤", "번", "식", "란", "더", "손", "술", "훔", "반", "빈", "실", "직", "흠",
                "흔", "악", "람", "뜸", "권", "복", "심", "헌", "엽", "학", "개", "롱", "평", "늘", "늬", "랑", "얀", "향", "울", "련"]
    
        return random.choice(last)
    
    # 사용시
    name = firstName() + LastName() + LastName()

     

    📨 랜덤 주소

    이번 더미데이터 생성의 핵심!

    주소 같은 경우에는 어떻게 생성해야하나 고민했는 데 구글링 하다가 전국 건물 주소를 제공하는 서비스가 있었다.

    이를 다운 받고 랜덤 줄을 받아와 파이프기준으로 list형태를 만들어 가공해 필요한 정보만 가져오는 식으로 정제하는 방법을 채택했다.

     

    -원 정보 형태 (아래 사이트에서 전국 시도 기준으로 txt 파일을 제공하고 있다. 참고로 2018년 때의 주소!!! 주의!!!) 

     

    주소정제전환서비스

    주소를 입력하세요. 정제된 도로명주소 및 지번주소로 자동 변환

    www.sujiewon.com

    - 위 파일을 사용하여 주소 추출

    def address():
        f_Ad = open('build_seoul.txt', 'r')
        random_line = random.choice(list(f_Ad.readlines())).splitlines()[0]
        f_Ad.close()
        return random_line
    
    # 사용시
    address_full = address()
    address_list = address_full.split('|')
    
    sido = address_list[1]
    sigungu = address_list[2]
    eupmyeondong = address_list[3]
    street = address_list[9]
    zipcode = address_list[19]
    

     

     

    🪪 랜덤 아이디와 이메일

    def loginId():
        n = random.randrange(5,10)
        loginId = ""
        for x in range(n):
            loginId += str(random.choice(string.ascii_lowercase))
    
        return loginId
    
     # 사용시
    login_Id = loginId()
    email = login_Id + "@google.com"
    
    

     

    🔒 랜덤 패스워드

    password_make = string.ascii_letters + string.digits
    
    def password():
        lenNum = 64
        password = "";
        for y in range(64) :
            password += random.choice(password_make)
    
        return password
    
    # 사용시
    password()
    

     

    📄 CSV파일로 변환

    password_make = string.ascii_letters + string.digits
    create_cnt = 1000
    
    f_Save = open('random_member.csv', 'w', encoding='utf-8', newline='')
    wr = csv.writer(f_Save)
    
    for i in range(create_cnt) :
        name = firstName() + LastName() + LastName()
        address_full = address()
        address_list = address_full.split('|')
    
        login_Id = loginId()
        email = login_Id + "@google.com"
    
        sido = address_list[1]
        sigungu = address_list[2]
        eupmyeondong = address_list[3]
        street = address_list[9]
        zipcode = address_list[19]
    
        wr.writerow([sido, sigungu, eupmyeondong, street, zipcode, email, login_Id, name, password()])
        print([sido, sigungu, eupmyeondong, street, zipcode, email, login_Id, name, password()])
    
    f_Save.close()
    

     

    🎉 결과물

     

     

    📌 사용 계기

    노출되면 안되는 중요 정보(다양한 계정 정보, JWT SecretKey 등) 가 담긴 Properties 파일을 암호화해서 올려야해 파일 암호화 기능을 사용하게 되었다.

     


    기본 설정

     

     

    📌 GPG Key만들기

    gpg --gen-key

     

     

    📌 Key를 Secring.gpg 형식으로 추출

    gpg --export-secret-keys -o secring.gpg

     

     

    📌 Key ID 포맷을 조회

    gpg --list-keys --keyid-format short

     

     

    📌 Key서버에 공개키 전송

    Key서버 종류는 다양하므로 안된다면 여러개를 시

    gpg --keyserver hkp://keyserver.ubuntu.com --send-keys [keyID]

     

     

     


    파일 암호화

     

     

    📌 해당 파일이 있는 폴더로 이동

     

    📌 암호화 하려는 파일 tar로 압축

    tar cvf [압축이름.tar] [암호화하려는 파일이름.확장자]

     

    📌 압축파일 암호

     gpg -c [암호화 할 압축파일]

    파워쉘로 안됨 ㅜㅜ

    git bash로 하니까 바로 됬다. 

    명령어치면 뜨는 창에 복호화에 사용할 비밀번호를 입력한다.

    비밀번호 재입력까지

     

    📌 이 암호한 파일을 github에 올리면 된다

     


    CD (github action) 용으로 사용

     

     

    📌  Actions secrets Key 등록

    .github > workflows > gradle.yml 스크립트에 복호화에 사용되는 변수 값의 이름에 맞게 키 이름을 설정해주고

    ${{ secrets.PROPERTIES }} 이니까 

      # 암호화 했던 설정파일 복호화
      - name: Decrypt applications properties
        run: gpg --quiet --batch --yes --always-trust --decrypt --passphrase=${{ secrets.PROPERTIES }} --output ./src/main/resources/application.tar ./src/main/resources/application.tar.gpg
        shell: bash

     

    github에서 secets에 PROPERTIES로 

    그 안의 값으로 파일 암호화할때 설정해줬던 비밀번호 값을 넣어 주면 된다.

     

     

     

     

     


    📌 규리님 없었으면 못했음 (사랑해요 규리찡) :

     

    Github에 파일 암호화해서 올리기👨🏻‍💻

    우리 팀 노션에 작성한 내용인데 같은 내용으로 고민하고있는 분이 있으실 수 있으니 참조하시라고 올립니다 ✨ Github Action을 이용한 CI/CD 구성시 프로젝트 빌드에 필요한 모든 파일이 깃허브에

    kyuu-ng.tistory.com

     

    'Programming > etc' 카테고리의 다른 글

    Postman 사용법  (0) 2022.11.28
    크롬 개발자 모드 (F12) 활용법  (0) 2022.11.15

    + Recent posts