728x90
반응형
728x90
반응형
반응형

요즘.. 디자인 패턴의 중요도 낮아짐

  • 개발환경 성숙: 프래임워크, 라이브러리 사용
    • 직접 구현할 필요 없음
  • 아키텍쳐의 변화: 모노리틱 -> MSA
    • 복잡성이 떨어지게 됨(구조같은데 좀 더 심플해짐)
  •  개발 언어의 변화(자바 1.8~)
    • 디자인패턴을 쓰지 않아도 해결할 수 있음(ex. 람다 등)

 

디자인 패턴?

소프트웨어 디자인 과정에서 자주 혹은 공통적으로 발생하는 문제들에 대해 재사용 가능한 해결책 혹은 형식화 된 가장 좋은 관행

 

알고리즘과의 차이점?

  • 실질적인 문제를 해결하기 위한 절차나 방법을 의미
    • 속도와 효율성(리소스)을 중요시(목표가 여기에 있음)
  • 패턴은 해결책에 대한 더 상위 수준의 설명 / 설계 구조 등 / 유지보수에 초점

 

바이블?ㅋ

왜 패턴?

  • 개발자 간의 원활한 협업이 가능
  • 소프트웨어의 구조를 파악하기 용이함
  • 재사용을 통해 개발 시간 단축
  • 설계 변경이 있을 경우 비교적 원활하게 조치가 가능

 

패턴에 대한 비판

  • 비 효율적인 해결책일 수 있음
    • Strategy패턴은 간단한 익명(람다) 함수로 구현할 수 있음
    • 많은 사람들이 이렇게 통합된 패턴들을 도그마처럼 신봉하여 패턴을 프로젝트의 맥락에 따라 적용하지 않고 문자 그대로 구현
    • 더 간단한 코드로도 문제 해결이 되는 상황에도 모든 곳에 패턴을 적용하려고 하는 패턴병에 걸리지 않도록 조심해야
    • 디자인 패턴은 모든 상황의 해결책이 아닌 코딩 방법론일 뿐이며 디자인 패턴에 얽매이지 말자

유지보수 쉽고 가독성 좋고 짧고 중복없고 객체지향 원칙에 맞는지 가 더 중요

 

객체지향 5대 원칙(SOLID)

> 높은 응집도와 낮은 결합도

  • SRP 단일 책임 원칙
    • proxy, aop
  • OCP 개방 폐쇄 원칙
    • 옵저버 패턴, if문 제거, 변경 발생 시 클래스 추가로 수정에는 닫혀있고 확장에는 열려있게
  • LSP 리스코프 치환
    • 다형성에 관한 원칙 제공; 상위 타입의 코드로 이뤄진 메서드에서 하위 타입으로 대신해도 메서드에는 아무 문제가 없어야 함
    • List list = new ArrayList(); <<- 받을 때 인터페이스로 하는게 맞다
  • ISP 인터페이스 분리 원칙
    • 거대 인터페이스를 쪼개서 기능별 인터페이스 분리
  • DIP 의존 역전 원칙
    • 인터페이스 호출; 구현체 변경으로 기능 변경 가능하도록 추상화에 의존해야함(구체화 의존 노노)

 

디자인 패턴 종류(23개)

https://m.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823 

 

[Design pattern] 많이 쓰는 14가지 핵심 GoF 디자인 패턴의 종류

디자인 패턴을 활용하면 단지 코드만 ‘재사용’하는 것이 아니라, 더 큰 그림을 그리기 위한 디자인도 재사용할 수 있습니다. 우리가 일상적으로 접하는 문제 중 상당수는 다른 많은 이들이 접

m.hanbit.co.kr

생성 패턴

특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 최소화 할 수 있도록 유연성 제공(ocp와 비슷)

 

추상 팩토리 패턴

  • (interface를 통해) 연관된 객체를 묶는데 초점

1. if else로 분기 겁나 많고 거기 안에서 로직 마니마니

2. switch..로 하고 거기 안에서 클래스호출(빈 등록이 계속되는 문제)

3. 중간 매소드인 팩토리 클래스를 만듦, 타입을 넘기고 거기 안에서 return service

맵으로 서비스를 가질 수 있음(Map<String, VehicleService> <- 빈 이름과 interface를 구현한 해당 빈)

나머지들이 그 interface를 구현

 

팩토리 매서드 패턴

  • 추상 팩토리 패턴과의 차이점
    • 팩토리 메서드 패턴은 어떤 객체를 생성할지에 집중
    • 추상 팩토리 패턴은 연관된 객체들을 모아둔다는 것에 집중(Factory)
  • 객체 생성을 서브클래스로 분리하여 위임(캡슐화)

 

빌더 패턴(@Builder)

  • 문제
    • 자바 빈즈 패턴(facet): data/getter/setter 사용
    • 점층적 생성자 패턴(생성자 파라미터 갯수별로 생성자 만드는거)
      • : 인자가 많아지면 생성자도 많아지지고
        • 3개 이상은 생성자 사용하지 말고 빌더를..
      • : 순서도 헷갈림
  • 빌더 패턴의 장점
    • 필요한 데이터만 설정
    • 유연성을 확보
    • 가독성을 확보
    • 불변성을 확보
      • 그 후에 set하면 안됨?? setter가 있으면 되고 없으면 안되고(setter를 선언하면 안되겠지)

 

프로토타입 패턴

  • JPA -> entity를 내리면 영속성이 유지될 수 있음
  • 별도의 dto 리턴(model mapper, mapstruct)하므로써 영속성을 끊는게 맞고
  • 변환하는 걸 프로토타입 패턴이라고 함
    • OSIV default true 

https://bangpurin.tistory.com/36

 

[jpa] OSIV란; spring.jpa.open-in-view

OSIV에 대한 이해를 하려면 영속성 컨텍스트가 뭔지 알아야한다. 이전 글을 참고하자. 2022.01.27 - [개발/spring] - [jpa] 영속성 컨텍스트 in spring [jpa] 영속성 컨텍스트 in spring 영속성 컨텍스트? 엔티티

bangpurin.tistory.com

Member member = memberService.saveMember(snsType, token);
member.setSnsType("kakao"); /////<<<<<<<-- 여기서 실수하면 반영됨 상하로 트랜잭션이 있어서
memberService.emptyLogic();

=> dto 리턴 시 해결!

 

싱글톤 패턴

  • 한 클래스마다 인스턴스를 하나만 생성하여 어디서든 참조
  • 병렬로 여러 스레드에서 돌릴 경우 조심
  • 직접 구현할 일 적음(스프링 빈)
  • 장점
    • 새로운 인스턴스를 계속해서 생성하지 않으므로 메모리 낭비가 적음
  • 단점
    • 코드량 많고, 테스트 어렵고, 자식 클래스 만들기 어렵, 클라이언트가 구체 클래스에 의존(dip 위반)
    • 스프링이 모두 해결해 줌!
public class SingletonService {
    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    // 생성자를 private으로 적용시켜 외부에서 인스턴스 생성을 막는다
    private SingletonService() {

    }
}

구조 패턴

클래스나 객체를 조합하여 더 큰 구조를 만들 수 있게 해줌

 

어댑터 패턴

  • 타사의 라이브러리를 호출부의 변경 없이 사용하고 싶을 경우
  • Array.AsList(), Collections.enumeration(), Collections.list() 등도 어댑터 패턴을 활용한 예시
@Service
@Primary //얘랑 동일한 빈이 있어도 이게 우선이다라고 해줘야(override)
public class MailAdapter implements MailSenderA {
    private final MailSolutionB mailSolutionB;

    @Override
    public void send(MailSolutionA.MailParam mailParam) {
        MailSolutionB.MailParam param = MailSolutionB.MailParam.builder().mailTitle(mailParam.getTitle())
                .mailBody(mailParam.getBody()).receiverEmail(mailParam.getEmail()).build();
        mailSolutionB.sendApi(param);
    }
}
  • 위 처럼 A -> B로 바꿀 때, 기존 호출부를 그대로 두고
  • A를 쏘는 것 처럼 보이지만 사실 B로 쏘는 것
  • Interface를 써야 구현체를 교체할 수 있음 그래야 primary로 오버라이딩이 가능
  • 모든 부분을 다 고치지 않고 한 곳만 고칠 때 좋음

 

브릿지 패턴

  • 기능의 구현 클래스를 런타임 때 자유롭게 지정이 가능
    • 주입을 set을 이용하여 동적으로 받음
  • 기능과 구현을 분리하여 구현이 변경되더라도 기능 클래스 부분에 대한 변경은 필요 없음
if (StringUtils.isNotEmpty(member.getAccountNumber())) {
    paymentService.setPaymentMethod(new CardMethodService()); // <<-- 서비스 주입을 런타임에
} else if (StringUtils.isNotEmpty(member.getPhoneNumber())) {
    paymentService.setPaymentMethod(new PhoneMethodService());
}
paymentService.pay1(order.getAmount(), member);

 

컴포지트 패턴

  • 일반적으로 직접 구현하지 않음
  • 클라이언트 전체와 부분을 구별하지 않고 동일한 인터페이스로 사용
  • 트리구조와 상당히 유사한 성격을 가지고 있다
  • 하지만 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려움

 

데코레이터 패턴

  • 일반적으로 직접 구현하지 않음
  • 기존 코드를 수정하지 않고도 행동을 확장 가능
  • 객체를 여러 데코레이터로 래핑하여 여러 행동들을 합성 가능
  • 데코레이터를 너무 많이사용하면 코드가 필요 이상으로 복잡해짐
  • file, buffered reader 등등 New로 만들어서 래핑해서 합성하는 것

 

퍼사드 패턴

  • 복잡한 로직들을 숨기고 간단한 인터페이스 함수만을 호출(추상화)
  • 우리는 이미 MVC패턴을 통해서 퍼사드 패턴을 알게 모르게 사용하고 있다.

 

플라이웨이트 패턴

  • 일반적으로 직접 구현하지 않음
  • 인스턴스가 필요할 때마나 매번 생성하는게 아니라 가능한 공유해서 메모리를 절약
    • 캐시, 레디스.. 디비 접근 최소화
  • String 객체를 생성하는 리터럴 방식의 String Constant Pool이 대표적인 플라이웨이트 패턴을 적용한 예시
  • https://www.baeldung.com/java-string-constant-pool-heap-stack
@Test
void 스트링_플라이웨이트패턴_테스트() {
    String str1 = "Hello";
    String str2 = "Hello";
    String str3 = new String("Hello");

    // 서로 같다!
    Assertions.assertSame(str1, str2);

    // 서로 다르다!
    Assertions.assertNotSame(str1, str3);

    // 서로 같다!
    Assertions.assertSame(str1, str3.intern());

    /*
        클래스가 JVM에 로드되면 모든 리터럴은 constant pool에 위치하게 된다.
        그리고 리터럴을 통해 같은 문자를 생성한다면 풀 안의 같은 상수를 참조하게 되는데 이를 String interning이라고 한다.
        String을 리터럴로 생성될 때 intern()이라는 메서드가 호출되고 이 intern() 메서드는
        constant pool에 같은 문자가 존재하는지 확인 후 존재한다면 그 참조 값을 가지게 된다.
     */
}
@Test
void Integer_플라이웨이트패턴_테스트() {
    Integer integer1 = Integer.valueOf("123");
    Integer integer2 = Integer.valueOf("123");

    // 서로 같다!
    Assertions.assertSame(integer1, integer2);


    Integer integer3 = Integer.valueOf("128");
    Integer integer4 = Integer.valueOf("128");

    // 서로 다르다!
    Assertions.assertNotSame(integer3, integer4);

    //직접 Integer.valueOf 함수 까보기!
    //-128 ~ 127 까지는 캐싱이라 같고 그 이외 값은 캐싱안하고 새롭게 인스턴스 생성이라 다름
}

 

프록시 패턴

  • SRP와 연관
  • 공통되는 로직 빼서 모듈화
  • AOP(aspect oriented programming; cglib etc..)
    • OOP로 독립적으로 분리하기 어려운 부가 기능을 모듈화 하는 방식
    • oop를 더 oop스럽게 해주는 방법
    • 객체지향을 더 객체지향스럽게 만들어 주는 프로그래밍
  • transactional, cache, test에 사용하는..
  • spring 큰 특징 : DI, AOP(aspect), PSA(portable service abstraction)
import com.example.pattern.order.service.OrderService;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.objenesis.SpringObjenesis;

@Configuration
public class ProxyConfig {
    private final SpringObjenesis objenesis = new SpringObjenesis();

    @Bean
    @Primary //order service말고 프락시를 빈으로 등록하도록 해야 aop 적용
    public OrderService orderServiceProxy() {
        return (OrderService) createCGLibProxy(OrderService.class, new MethodCallLogInterceptor());
    }

    private Object createCGLibProxy(Class<? extends Object> targetClass, MethodInterceptor interceptor) {
        // Create the proxy
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(interceptor);
        return enhancer.create();
    }
}

행동 패턴

반복적으로 사용되는 객체들의 커뮤니케이션을 패턴화, 결합도를 최소화 하는 것이 목적

 

책임 연쇄 패턴

스프링 필터 사용(필터 체인)

  • 검증 절차는 자유롭게 추가될 수 있어야 하고, 순서도 자유롭게 변경할 수 있어야 할 때 
  • 기존 클라이언트 코드를 수정하지 않고 앱에 새 핸들러를 도입 가능
@Configuration
public class FilterConfig {
    //    @Bean
//    @Order(0)
    public FilterRegistrationBean userAgentCheckFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new UserAgentCheckFilter());
        registrationBean.addUrlPatterns("/orders/*");
        return registrationBean;
    }

    //    @Bean
//    @Order(1)
    public FilterRegistrationBean memberCheckFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new MemberCheckFilter());
        registrationBean.addUrlPatterns("/orders/*");
        return registrationBean;
    }
}

 

커맨드 패턴

  • 잘 사용하지 않음
  • 기존 클라이언트 코드를 수정하지 않고 새로운 커맨드들을 도입 가능
  • 요청부와 동작부를 분리시켜 주기 때문에 결합도를 낮출 수 있음
  • 보통 비동기
@Test
void 혜택_발송_스레드_테스트() throws InterruptedException {
    int numberOfThreads = 5;
    CountDownLatch latch = new CountDownLatch(numberOfThreads);
    //ExecutorService 객체가 Invoker를 의미
    ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
    for (int i = 0; i < numberOfThreads; i++) {
        //Runnable 구현 객체가 Command를 의미;
        // runnable interface 특징? 구현할 함수가 1개, callable과 디르게 반환타입이 없음
        //CouponApiService 객체가 Receiver를 의미
        Runnable doThread = new CouponService(new CouponApiService());
        //위 대신 lambda로 대체가능
        executorService.execute(() -> {
            doThread.run();
            latch.countDown();
        });
    }
    latch.await();
}

Runnable -> void

Callable -> return 있음

 

반복자 패턴

  • 잘 사용하지 않음
  • 혜택이 아이템을 어떻게 구현하였는지 호출하는 쪽에서는 알 필요가 없음
    • 즉 캡슐화가 잘 되어 있음
  • 내부 구현을 외부로 노출시키지 않으면서도 모든 항목에 접근 가능
  • 적용 시 덜 효율적이거나 과도하지는 않은지 확인

 

옵저버 패턴

  • 한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때 사용(Pub/Sub 패턴이라고도 함)
  • message push, pull 하는 방식을 통해 결합도를 낮출 수/없앨 수 있음 
    • rabbitMQ, kafka 등
  • 느슨한 결합으로 객체간의 의존성 제거
  • 너무 많이 사용하게 되면 상태 관리가 힘듦
private final ApplicationEventPublisher eventPublisher;

//함수 내부에서 호출 publish
   eventPublisher.publishEvent(new OrderEvent(order.getId(), OrderType.CREATE));
   
//

import com.example.pattern.order.model.OrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Async
public class OrderListener {
    @EventListener
    public void onApplicationEvent(OrderEvent event) {
        log.info("주문이벤트 Published [orderId : {}, orderType : {}]", event.getOrderId(), event.getOrderType().toString());
        log.info("이후 리뷰 알림 발송이 시작됩니다.");
        log.info("이후 정산 API 호출이 시작됩니다.");
    }
}

 

중재자 패턴

  • 옵저버 패턴
    • 1개의 publish에 대해 N개의 subcriber가 존재하고, observer가 pulling 이나 push방식을 통해 관리
  • 중재자 패턴
    • M개의 publisher와 N개의 subcriber 사이에서 1개의 mediator를 통해 통신

 

메멘토 패턴

  • 상태정보 저장 관련 메모리에 들고 있을 일 없음; 잘 사용하지 않음
  • 캡슐화를 위반하지 않고 객체의 state 스냅샷을 생성할 수 있음
  • 메멘토를 너무 자주 생성하면 많은 Ram을 소모할 수 있음

 

상태 패턴

  • 잘 사용하지 않음

 

전략 패턴

  • 하나의 메시지와 책임을 정의하고 이를 수행할 수 있는 다양한 전략을 만든 후, 다형성을 통해 전략을 선택해 구현을 실행
  • 유사한 패턴들
    • 팩토리, 추상 팩토리, 브릿지, 커맨드 패턴 등 이미 사용 중
  • 특징
    • OCP 준수, 하지만 과도하게 복잡해질 수 있음

 

템플릿 메서드 패턴

  • 잘 사용하지 않음, 요즘은 implement
    • interface는 내부 instance를 둘 수 없음 -> 필요 시 abstract class 를 사용
  • 상속을 통해 슈퍼 클래스의 기능을 확장할 때 사용하는 대표적인 방법
  • 변하지 않는 기능은 슈퍼 클래스에 만들어 두고, 확장할 기능은 서브 클래스에서 만듦
  • 단점
    • 알고리즘 변경 시 거의 모든 클래스에 수정이 가해질 수 있음
    • 상속의 단점.. 결합이 커서 
public abstract class Review { //abstract 사용예시
    public void review() {
        //부모 클래스에서 알고리즘의 골격을 정의
        login();
        selectBooks();
        putContent();
        selectEvaluation();
    }

    public void login() {
        log.info("로그인 성공!");
    }

    public void selectBooks() {
        log.info("리뷰 대상 상품 선택");
    }

    public void selectEvaluation() {
        log.info("별점 선택");
    }

    public abstract void putContent(); //

}

 

방문자 패턴

  • 잘 사용하지 않음
  • 맴버는 맴버 클래스로, 리워드는 리워드 클래스로.. 둘을 섞지 않는다
  • OCP : 다른 클래스를 변경하지 않으면서 새로운 행동 도입 가능
  • SRP : 같은 행동의 여러 버전을 같은 클래스로 이동할 수 있음
public class PointBenefit implements Benefit {
    @Override
    public void getBenefit(GoldMember member) {
        log.info("골드 멤버를 위한 포인트 제공 혜택");
    }

    @Override
    public void getBenefit(VIPMember member) {
        log.info("VIP 멤버를 위한 포인트 제공 혜택");
    }

    @Override
    public void getBenefit(SilverMember member) {
        log.info("실버 멤버를 위한 포인트 제공 혜택");
    }
}
public class GoldMember implements Member {
//    public void point() {
//        log.info("골드 멤버를 위한 포인트 제공 혜택");
//    }
//    public void discount() {
//        log.info("골드 멤버를 위한 할인 혜택");
//    }

    @Override
    public void getBenefit(Benefit benefit) {
        benefit.getBenefit(this);
    }
}
728x90
반응형
반응형

환경: java8

엑셀 다운로드 기능을 서버에 배포하면 NPE가 나면서 안되는 현상, 특이사항으로는 배포 시 되는 서버가 있고 안되는 서버가 있었음

org.apache.poi.hssf.usermodel.HSSFSheet.autoSizeColumn(HSSFSheet.java:1937)

 

에러 발생 코드

workbook.getSheetAt(0).autoSizeColumn(colIndex);

 

소스 상 딱히 null이 날 곳이 없었다.

 

되는 곳이 있고 안되는 곳이 있어, 환경의 문제에서 나온게 아닌가 싶어 구글링 하던 중 아래 글들을 보게된다.

설마 싶어 되는 서버와 안되는 서버의 자바 내 폰트 설정파일을 확인해보니

진짜 폰트 설정 파일(fontconfig.properties)이 있는 곳에서는 되고 없는 곳에서는 안되었다..

 

서버 담당자한테 말했더니 아래를 설치해주셨고, 설정 파일이 생기면서 이슈 해결

sudo yum install fontconfig dejavu-sans-fonts dejavu-serif-fonts
vi /usr/local/jdk8/jre/lib/fontconfig.properties

-----------

version=1
sequence.allfonts=default

참고

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=amabile29&logNo=222292712456 

 

OpenJDK8 version upgrade - Font Issue

OpenJDK에는 폰트 파일이 있지 않아서 업그레이드 후 Excel 파일 생성 등 폰트가 필요한 부분에서 에...

blog.naver.com

 

https://github.com/adoptium/adoptium-support/issues/70

 

There was no fonts in Linux version · Issue #70 · adoptium/adoptium-support

From @JamesMung on October 23, 2018 3:54 JDK Version ; jdk8u181-b13 When I use jasper print PDF in Windows is fine, but for Linux found the error as follow: Caused by: net.sf.jasperreports.engine.J...

github.com

 

728x90
반응형

'개발 > java' 카테고리의 다른 글

[java] orElse vs orElseGet  (0) 2023.10.11
디자인 패턴  (0) 2023.07.11
[java] lambda stream and final  (0) 2023.02.06
[google admob] ssv 콜백 적용  (0) 2023.01.06
[Date] java8 이하에서 날짜 timezone 변환  (0) 2022.11.04
반응형

이슈

- build 시 intelliJ면 아래와 같은 에러가 남

- gradle 일 경우 에러 안 남

 

해결

- interface일 경우 @PathVariable에 name 을 명시

 

참고

https://shanepark.tistory.com/331

728x90
반응형
반응형

컴포넌트

  • 컴포넌트는 시스템의 구성 요소로 배포할 수 있는 가장 작은 단위이며, 자바의 경우 jar 파일이 컴포넌트임
  • 컴포넌트는 다양한 형태로 만들어질 수 있음
    • 여러 컴포넌트를 서로 링크하여 실행 가능한 단일 파일로 생성할 수 있음
    • 여러 컴포넌트를 묶어서 war 파일과 같은 단일 아카이브로 만들 수 있음
    • 컴포넌트 각각을 jar과 같이 동적으로 로드 할 수 있는 플러그인이나 실행 가능한 파일로 만들어 독립적으로 배포할 수 있음
  • 컴포넌트가 최종적으로 어떤 형태로 배포되든, 잘 설계된 컴포넌트라면 반드시 독립적으로 개발 가능해야 함
  • 참고. 앞으로 내용을 따라갈 때 거시적인 관점에서 하나의 큰 시스템을 생각하며 흐름을 따라가야 함.

컴포넌트 응집도

  • 어떤 클래스를 어느 컴포넌트에 포함시켜야 할지는 중요한 결정이므로, 제대로 된 소프트웨어 엔지니어링 원칙이 필요함

 

REP(reuse/release equivalance principle): 재사용/릴리스 등가 원칙

  • 정의: 재사용 단위는 릴리스 단위와 같다.
    • 이는 당연한데, 컴포넌트가 릴리스 절차를 통해 관리되지 않거나, 릴리스 번호가 없다면 재사용하고 싶지도, 할 수도 없음
    • 릴리스 번호가 없다면 컴포넌트들이 호환되는지 보증할 수 없음
    • 개발자는 새로운 버전이 언제 출시되고 무엇이 변했는지 알아야 함(새로운 버전으로의 통합 여부 및 시기 결정)
  • REP를 소프트웨어 설계와 아키텍처 관점에서 보면 다음과 같음
    • 단일 컴포넌트는 응집성이 높은 클래스와 모듈들로 구성되어야 함
    • 이를 다르게 보면 하나의 컴포넌트로 묶인 클래스와 모듈은 반드시 함께 릴리스 할 수 있어야 한다는 것
    • 하나의 컴포넌트로 묶인 클래스와 모듈은 버전이 같고, 동일한 릴리스로 관리되고, 동일한 릴리스 문서에 포함되어야 함
  • 이 원칙 만으로는 클래스와 모듈을 단일 컴포넌트로 묶는 방법을 제대로 설명하지 못하지만(약점), 이 원칙의 약점은 다음 두 원칙(CCP와 CRP)으로 보완할 수 있음

 

CCP(common closure principle): 공통 폐쇄 원칙

  • 정의: 동일한 이유로 동일한 시점에 변경되는 클래스는 같은 컴포넌트로 묶고, 다른 시점에 다른 이유로 변경되는 클래스는 분리하라
  • 대다수의 애플리케이션에서 유지보수성은 재사용성보다 훨씬 중요하며, 변경은 단일 컴포넌트에서 발생해야 함(독립적인 배포)
    • 수정이 필요한 경우 모든 컴포넌트를 조금씩 수정하기 보다는 하나의 컴포넌트만 수정하도록 하는게 낫다.
  • OCP(개방 폐쇄 원칙)는 class level이고, CCP는 컴포넌트 level.
    • OCP: 공통적인 변경에 대해 클래스가 닫혀 있도록 설계.
  • SRP(단일 책임 원칙) class level이고, CCP는 컴포넌트 level : 단일 컴포넌트는 변경의 이유가 여러 개 있어서는 안됨
    • SRP: 서로 다른 이유로 변경되는 메소드를 서로 다른 클래스로 분리하라
    • CCP: 서로 다른 이유로 변경되는 클래스를 서로 다른 컴포넌트로 분리하라.

 

CRP(common reuse principle): 공통 재사용 원칙

  • 정의: 컴포넌트 사용자들을 필요로 하지 않는 것에 의존하게 강요하지 말라.
  • CRP도 클래스와 모듈을 어느 컴포넌트에 위치시킬지 결정할 때 도움이 됨
  • 같이 재사용되는 경향이 있는 클래스와 모듈들은 같은 컴포넌트에 포함
    • CRP에서는 재사용한 클래스들의 연결고리가 동일한 컴포넌트에 포함되어 있어야
  • 컴포넌트를 의존하겠다고 결정한다는 것은 생각보다 많은 유지보수가 필요할 것을 암시하며 CRP에 의거하면 동일 컴포넌트로 묶어서는 안되는 것을 의의함
    • 컴포넌트의 단 하나의 클래스만을 사용한다고 해서 의존성이 약해지는게 아님
      • 의존성은 이분법적인 개념(Y/N) 이지 %가 아님
    • 이로 인해 사용되는 컴포넌트가 변경될 때마다 같이 변경(재배포 등)해야 할 가능성이 높음
    • 그러므로 의존하는 컴포넌트가 있다면 해당 컴포넌트의 모든 클래스에 대해 의존함을 확실히 인지해야 함
  • CRP는 강하게 결합되지 않은 클래스들을 동일한 컴포넌트에 위치시켜서는 안 된다고 말함
  • ISP(인터페이스 분리 원칙)는 class level, CRP는 컴포넌트 level : 필요하지 않은 것에 의존하지 말라
    • ISP: 사용하지 않는 메소드가 있는 클래스에 의존하지 말라
    • CRP: 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라

 

컴포넌트 응집도에 대한 균형 다이어그램

  • 세 원칙은 서로 상충되는데, REP와 CCP는 포함 원칙(컴포넌트를 크게 만듦)이며, CRP는 배제 원칙(컴포넌트를 작게 만)임
  • 뛰어난 아키텍트는 3가지 원칙들이 균형을 이루는 방법을 찾아야 함.

응집도에 관한 세 원칙이 어떻게 상호작용하는가

 

  • 각 변은 반대쪽 꼭지점에 있는 원칙을 포기했을 때 감수해야 할 비용을 나타냄
    • CCP를 포기하면, 사소한 변경이 생겼을 때 너무 많은 컴포넌트에 영향이 미침
    • CRP를 포기하면 불필요한 릴리스가 너무 빈번해짐
    • REP를 포기하면 재사용이 어려워짐
  • 일반적으로 삼각형의 오른쪽에서 시작해서 왼쪽으로 이동해 감
    • 프로젝트 처음부터 재사용성이 필요하지는 않다가 성숙해지다보면 점점 재사용성(REP)이 중요해짐
  • 프로젝트의 컴포넌트 구조는 시간과 성숙도에 따라 변한다.

컴포넌트 결합

ADP(Acyclic dependency principle): 의존성 비순환 원칙

  • 정의: 컴포넌트 의존성 그래프에 순환이 있어서는 안된다.
  • 많은 개발자가 동일한 소스 파일을 수정하는 환경에서 코드가 동작하지 않게 될 수 있으며, 2가지 해결방법이 발전되어 옴
    • 주단위 빌드
    • 순환 의존성 제거하기

 

주 단위 빌드

  • 4일은 서로를 신경쓰지 않고, 금요일이 되면 코드를 통합하여 시스템을 빌드함
  • 프로젝트가 커지면서 통합에 드는 시간이 계속해서 늘어나게 됨
  • 결국 빌드 일정을 늘려야 하고, 통합과 테스트는 수행하기 점점 어려워지며, 빠른 피드백이 주는 장점을 잃게됨

 

순환 의존성 제거하기

  • 이 문제의 해결책은 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리하는 것
  • 이를 통해 컴포넌트는 개별 관리자 또는 단일 개발팀이 책임질 수 있는 작업 단위가 됨
  • 개발자가 해당 컴포넌트가 동작하도록 만든 후, 릴리스하여 다른 개발자가 사용할 수 있도록 만듬
  • 이는 단순하며 합리적이라 널리 사용되지만 컴포넌트 사이의 의존성 구조를 반드시 관리해야 함
  • 의존성 구조에 순환이 있어서는 안되며, 컴포넌트 간의 의존성은 비순환 방향 그래프(DAG, Directed Acyclic Graph)여야 함

oneway

  • 어느 컴포넌트에서 시작하더라도 의존성 관계를 따라 최초의 컴포넌트로 돌아갈 수 없음
  • Presenters를 담당하는 팀에서 새로운 릴리스를 만들면 이 릴리스에 영향받는 팀을 쉽게 찾을 수 있음
  • Main은 새로 릴리스되더라도 시스템에서 영향받는 컴포넌트가 없음
  • 시스템 전체를 릴리스한다면 릴리스 절차는 상향식으로 진행됨(Entities부터 시작해 Main은 마지막에 처리)

이처럼 구성요소 간 의존성을 파악하고 있으면 시스템을 빌드하는 방법을 알 수 있음

 

순환이 컴포넌트 의존성 그래프에 미치는 영향

circular

  • 요구사항으로 Entities에 포함된 클래스 하나가 Authorizer의 클래스 하나를 사용할 수 밖에 없다면 순환 의존성이 발생함
  • Database 컴포넌트 개발자는 컴포넌트를 릴리스 하려면 Entities, Authorizer, Interactors 모두와 호환되어야 함
  • 세 컴포넌트는 하나의 거대 컴포넌트가 되며, 개발자 서로가 얽매여 모두 항상 정확하게 동일한 릴리스를 사용해야 함
  • Entities를 테스트하려면 Authorizer와 Interactors도 빌드하고 통합해야 하면서 어려워짐
  • 모듈의 개수가 많아짐에 따라 빌드 관련 이슈는 기하급수적으로 증가함
  • 컴포넌트를 어떤 순서로 빌드해야 올바른지 파악하기 힘들어지며, 올바른 순서라는 것 자체가 없을 수 있음

 

순환 끊기

  • 컴포넌트 사이의 순환을 끊고, DAG로 복구하는 것은 언제든 가능하며, 의존성 역전 원칙 또는 새로운 컴포넌트 생성으로 가능함
  • 의존성 역전 원칙(DIP)
    • User가 필요로 하는 메소드를 제공하는 인터페이스(permissions in Entity)를 제공함
    • 그리고 이 인터페이스는 Entities에, 구현체는 Authorizer에 위치시킴

DIP

 

  • 새로운 컴포넌트 생성
    • Entities와 Authorizer가 의존하는 새로운 컴포넌트를 만듬
    • 그리고 두 컴포넌트가 모두 의존하는 클래스들을 새로운 컴포넌트로 이동시킴

 

흐뜨러짐(Jitters)

  • 두 번째 해결책(새로운 컴포넌트 생성)이 시사하는 바는 요구사항이 변경되면 컴포넌트 구조도 변경될 수 있다는 사실
  • 실제로 애플리케이션이 성장하면서 컴포넌트 의존성 구조는 서서히 흐트러지며 또 성장함
  • 따라서 의존성 구조에 순환이 발생하는지를 항상 관찰해야 하며, 어떤 식으로든 끊어내야 함

 

하향식(top-down) 설계

  • 프로젝트 초기에는 컴포넌트 구조를 설계할 수 없음. 즉, 컴포넌트 구조는 하향식(top-down)으로 설계될 수 없음
    • 컴포넌트 의존성 다이어그램은 애플리케이션 기능과는 거의 관련이 없고, 빌드 가능성과 유지보수성의 지도와 같음
    • 컴포넌트는 시스템에서 가장 먼저 설계할 수 있는 대상이 아니며, 시스템이 성장하고 변경될 때 함께 진화함
  • 하지만 모듈들이 점차 쌓이기 시작하면 의존성 관리에 대한 요구가 점차 늘어남
    • 변경되는 범위가 시스템의 가능한 한 작은 일부로 한정되기를 원함
    • 함께 변경되는 클래스는 같은 위치에 배치시킴: 단일 책임 원칙(SRP), 공통 폐쇄 원칙(CRP)
    • 의존성 구조와 관련된 최우선 관심사는 변동성의 격리(자주 변경되는 컴포넌트로 부터 다른 컴포넌트를 보호함)
  • 애플리케이션이 계속 성장하면서 재사용 가능한 요소를 만드는 일에 관심을 기울이기 시작함: 공통 재사용 원칙(CRP)
    • 결국 순환이 발생하면 컴포넌트 의존성 그래프는 조금씩 흐트러지고 또 성장함: 의존성 비순한 원칙(ADP)

"아무런 클래스도 설계하지 않은 상태에서 컴포넌트 의존성 구조를 설계하려고 시도하면 큰 실패를 맛볼 수 있다. 공통 폐쇄 원칙에 대해 그다지 파악하지 못하고 있고, 재사용 가능한 요소도 알지 못하며, 컴포넌트를 생성할 때 거의 확실히 순환 의존성이 발생할 것이다. 따라서 컴포넌트 의존성 구조는 시스템의 논리적 설계에 발맞춰 성장하며 또 진화해야 한다."

 

SDP: 안정된 의존성 원칙

  • 정의: 안정성의 방향으로(더 안정된 쪽에) 의존하라.
  • 변경이 어려운 컴포넌트는 최대한 독립적으로
  • 변경이 어려운 컴포넌트에 한번 의존하게 되면 변동성이 큰 컴포넌트도 결국 변경이 어려워짐
  • 즉, 변경하기 쉽도록 모듈을 설계해도 이 모듈에 누군가가 의존성을 메달아 버리면 이 모듈도 변경하기 어려워짐

 

안정성(stability)

  • 안정성은 변경의 발생 빈도와는 직접적인 관련이 없고, 변경을 위해 필요한 작업과 관련됨
  • 안정적이라는 것은 변경을 위해 상상한 수고를 감수해야 한다는 것
    • 컴포넌트를 변경하기 어렵게 만드는 많은 요인(컴포넌트의 크기, 복잡도, 간결함 등)이 존재하는데, 이중 다른 컴포넌트가 해당 컴포넌트에 의존하게되면 변경이 특히 어려워짐
    • 왜냐하면 사소한 변경이라도 의존하는 모든 컴포넌트를 만족시키면서 변경하려면 상당한 노력이 들기 때문임

 

 

x is stable

  • X는 안정된 컴포넌트인데, 세 컴포넌트가 X에 의존하며 X는 변경하지 말아야 할 이유가 3가지나 됨
  • 이때 X는 세 컴포넌트를 책임진다고 말하며, 반대로 X는 어디에도 의존하지 않음
  • X가 변경되도록 만들 수 있는 외적인 영향이 전혀 없으므로, X는 독립적이라고 말함

 

t is unstable

  • 아래의 Y는 상당히 불안정한 컴포넌트임
  • 어떤 컴포넌트도 Y에 의존하지 않으므로 Y는 책임성이 없음
  • Y는 세 컴포넌트에 의존하므로 변경이 발생할 수 있는 외부 요인이 3가지이므로, Y는 의존적이라고 함

 

안정성 지표

  • 컴포넌트로 들어오고 나가는 의존성의 개수를 통해 컴포넌트의 불안정성(I)을 계산할 수 있음
    • fan-in: 안으로 들어오는 의존성으로 컴포넌트 내부의 클래스에 의존하는 컴포넌트 외부의 클래스 개수
    • fan-out: 바깥으로 나가는 의존성으로 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 개수
    • 불안정성(I)은 fan-out / (fan-in + fan-out)으로 계산 가능하며, [0, 1] 사이의 값을 가짐
  • 불안정성(I)가 0인 경우(X)
    • 해당 컴포넌트에 의존하는 다른 컴포넌트는 있지만, 해당 컴포넌트 자체는 다른 컴포넌트에 의존하지 않음
    • 이는 컴포넌트가 가질 수 있는 최고로 안정된 상태이며, 이러한 컴포넌트는 다른 컴포넌트를 책임지며 독립적임
    • X에게 의존하는 컴포넌트가 있으므로 변경이 어렵지만, X를 강제하는 의존성은 갖지 않음
  • 불안정성(I)가 1인 경우(Y)
    • 어떤 컴포넌트도 해당 컴포넌트에 의존하지 않지만, 해당 컴포넌트는 다른 컴포넌트에 의존함
    • 최고로 불안정한 상태이며 책임성이 없으므로 의존적임
    • 의존하는 컴포넌트가 없으므로 변경하지 말아야 할 이유가 없음
    • Y가 다른 컴포넌트에 의존한다는 뜻은 Y를 변경할 이유가 있다는 것임

 

모든 컴포넌트가 안정적이어야 하는 것은 아니다

  • 우리가 기대하는 것은 불안정한 컴포넌트와 안정된 컴포넌트가 모두 존재하는 상태

 

  • 위 다이어그램은 세 컴포넌트로 구성된 시스템이 갖는 이상적인 구조임
  • 상단에는 변경 가능한 컴포넌트들이 있고, 하단의 안정된 컴포넌트에 의존함
  • 위로 향하는 화살표가 있으면 안정된 의존성 원칙(SDP)에 위배되는 것인데, 존재하지 않음

 

  • Flexible은 변경하기 쉽도록 설계한 컴포넌트임
  • 우리는 Flexible이 불안정한 상태이기를 바라지만, Stable에서 Flexible에 의존성을 걸게 되면 SDP를 위배함
  • Flexible을 변경하려면 Stable과 Stable에 의존하는 나머지 컴포넌트에도 조치를 취해야 함

 

  • 이를 해결하려면 Flexible에 대한 Stable의 의존성을 끊어야 함
  • 예를 들어 Stable의 내부 클래스 U가 Flexible의 내부 클래스 C를 사용할 때, DIP를 도입하면 이 문제를 해결할 수 있음

 

 

DIP 적용

  • US라는 인터페이스를 생성하고 이를 UServer 컴포넌트에 넣은 후 C가 해당 인터페이스를 구현하도록 만듦
  • 이를 통해 Flexible에 대한 Stable의 의존성을 끊고, 두 컴포넌트는 모두 UServer에 의존하도록 강제함
  • UServer는 매우 안정되며(I=0) Flexible은 불안정성(I=1)을 유지할 수 있고, 모든 의존성은 I가 감소하는 방향으로 향함
    • 오로지 인터페이스만을 포함하는 컴포넌트(UServer)를 생성하는 방식이 이상하게 보일 수도 있음
    • 하지만 자바와 같은 정적 타입 언어에서는 이 방식이 흔히 사용되며 꼭 필요한 전략으로 알려져 있음
    • 이러한 추상 컴포넌트는 상당히 안정적이며, 따라서 덜 안정적인 컴포넌트가 의존할 수 있는 이상적인 대상임

 

SAP: 안정된 추상화 원칙

  • 정의: 컴포넌트는 안정된 정도만큼만 추상화되어야 한다.

고수준 정책(자주 변경해서는 안되는 소프트웨어)을 어디에 위치시켜야 하는가?

  • 고수준 정책을 캡슐화하는 소프트웨어는 안정된 컴포넌트에, 변동성이 큰 소프트웨어는 불안정한 컴포넌트에 포함시켜야 함
  • 하지만 고수준 정책을 안정된 곳에 위치시키면, 그 정책을 포함하는 소스 코드 수정이 어려워져 시스템 전체 아키텍처가 유연성을 잃음
  • 해결: 개방 폐쇄 원칙(OCP)
    • 이 원칙을 준수하는 클래스가 추상 클래스

안정된 추상화 원칙(SAP)

  • 안정된 추상화 원칙은 안정성과 추상화 정도 사이의 관계를 정의
    • 안정된 컴포넌트는 추상 컴포넌트여야 하며, 이를 통해 안정성이 컴포넌트를 확장하는 일을 방해해서는 안됨
    • 불안정한 컴포넌트는 반드시 구체 컴포넌트로써, 컴포넌트가 불안정하므로 내부의 구체적인 코드를 쉽게 변경할 수 있어야 함
  • 안정된 추상화 원칙(SAP)와 안정된 의존성 원칙(SDP)를 결합하면 컴포넌트에 대한 의존성 역전 원칙(DIP)

 

추상화 정도 측정하기

  • A = Na / Nc
    • Nc는 컴포넌트의 클래스 개수다.
    • Na는 컴포넌트의 추상 클래스와 인터페이스 개수다.
  • A = 0 : 컴포넌트에 추상 클래스가 하나도 없다.
  • A = 1 : 오로지 추상 컴포넌트만 있다.

 

주계열

  • 안정성(I)과 추상화 정도(A) 사이의 관계를 표현하면 다음과 같음
  • 최고로 안정적이며 추상화된 컴포넌트는 좌측 상단(0,1)
  • 최고로 불안정하며 구체화된 컴포넌트는 (1,0)에 위치함
  • 모든 컴포넌트가 이 두 지점에 위치하지는 않으며, 컴포넌트는 추상화와 안정화의 정도가 다양함
  • 컴포넌트가 위치할 수 있는 합리적인 궤적을 표현하면 다음과 같음

 

  • 고통의 영역(Zone of Pain)
    • (0, 0) 주변 구역에 위치한 컴포넌트들
    • 매우 안정적이며 구체적인데, 컴포넌트가 뻣뻣한 상태이므로 바람직하지 않음
    • 추상적이지 않으므로 확장이 어렵고, 안정적이므로 변경이 어려움
    • 제대로 설계된 컴포넌트라면 여기에 위치하지 않으며, 배제해야 하는 구역임
    • ex) 데이터베이스 스키마 or String 클래스(String은 변동성이 없으므로 해롭지는 않음) 등
  • 쓸모없는 구역(Zone of Uselessness)
    • (1, 1) 주변 구역에 위치한 컴포넌트들
    • 최고로 추상적이지만, 누구도 그 컴포넌트에 의존하지 않음(쓸모 없음)
    • 이는 누구도 구현하지 않은 채 남겨진 추상클래스인 경우가 많음
  • 주계열(The Main Sequence)
    • 변동성이 큰 컴포넌트들을 두 배제 구역으로부터 가능한 멀리 떨어뜨리는 선분
    • 쓸모없지 않으면서도 심각한 고통을 안겨주지도 않음
    • 가장 바람직한 지점은 주 계열의 종점이긴 하지만 일부 컴포넌트는 불가능할 수 있음
    • 주계열 바로 위에 또는 가깝게 위치할 때 가장 이상적

 

주계열과의 거리

  • 주계열로부터 얼마나 떨어져 있는지 측정하는 지표.
  • D=| A + I -1 |
  • D가 0에 가까울수록 이상적(주계열 선 위)
  • D가 0에서 가깝지 않다면 해당 컴포넌트는 재검토한 후 재구성하도록 계획 가능
    • D지표의 평균과 분산을 통해 다른 컴포넌트에 비해 예외적인 컴포넌트 추출 가능 -> 리팩

 

컴포넌트 산점도

 

  • 반대로 한 컴포넌트의 D를 시간 별(릴리즈 별)로 측정하여 주계열에서 멀리 떨어진 시점에 들어간 feature에 대해 리뷰 가능

 

  • 의존성 관리 지표는 설계의 의존성과 추상화 정도가 내가 “흘륭한” 패턴이라고 생각하는 수준에 얼마나 잘 부합하는지를 측정함
  • 지표는 임의로 결정된 표준을 기초로 한 측정값에 지나지 않기에, 다음 단계에 대한 힌트로 사용하면 충분

 

 

728x90
반응형
반응형

gshell은 bash의 상위호환 bash와 충돌나는 문법 없음

cshell은 구버전

 

Unix Shell
• Kernel을 둘러싸고 있는 껍데기
• 사용자가 제어할 수 있는 명령행 해석기

Bourne Shell
• 1977, Stephen Bourne
• /bin/sh

BASH (Bourne Again Shell)
• 1989, Brian Fox
• /bin/bash

 

ssh -> secure shell

-> 이걸로 게이트웨이까지 가고 그 이후론

rlogin -l 아이디 호스트명

이걸로 로그인

 

pwd: present working directory

id

uname: unix system  name -> os 정보 아키텍쳐 등

hostname

 

배쉬 내부 명령

command line argument(parameter)

echo는 기본적으로 new line이 나가기 때문에 그걸 무시하려면 -n옵션

 

ls

ls -1 파일 이름만 한줄에 하나씩

-a 숨겨진 것도

-F 파일

-l 롱포맷

 

ls 중 어떤 규칙만 필터 치고 싶다면..(패턴)

glob pattern

  • 임의의 길이(wildcard) *
  • 임의의 한 글자 ?

ls * -> 전체

ls .* -> 히든 전체

ls [a-f]*.txt -> a~f로 시작하는 텍스트 파일

ls *.tx? -> .txa 등의 확장자를 가진 파일

 

파일 시스템

  • File
    • OS에서 데이터를 디스크와 같은 저장소에 저장해 둘 때의 단위 형태
    • 넓은 의미에서는 일반 파일, 디렉토리와 각종 입출력 장치를 모두 파일로 간주
  • Directory
    • 파일을 담아두는 공간 (폴더)
    • 디렉토리 안에 디렉토리를 담을 수 있기 때문에 트리 구조로 형상화

 

cd : change directory

pwd

~ : 홈디렉토리

ls ~ 아이디 : 다른사람의 홈디렉토리

 

history

히스토리 중 다시 실행하고 싶다면

!12 : 히스토리 중 12번 재실행

!find : 히스토리 중 마지막 find로 실행했던 구문 재실행

!! : 바로 직전 명령 재실행

!$ : 바로 직전 명령의 마지막 argument

 

직전 명령 편집

echo "hello world"

^hello^java :hello -> java 변환(^ 캐럿이라고 부름)

 

touch

빈 파일 만들기, last modify date 바꿀 때

 

mkdir : make directory

rmdir : remove directory 빈 디렉토리만 삭제

 

cp 복사

mv 장소 이동, 이름 변경

rm 파일 삭제

rm -r : recursive하게 안에서부터 쫙 지우고 마지막으로 폴더 삭제

file /bin/gzip(파일) : 어떤 파일인데? 확인하는 용도

 

변수

사용자 변수
• 보통 소문자로 명명
• x=1
• x="hello world"
• x=hello world   (x)

주의사항

  • = 주위에 공백 없음

 

값을 꺼내쓰기(de-reference)

echo $x / ${x} / "$x"

 

single quote

  • literal, 문자열
  • 치환이 안됨

double quote(기본적으로 이거 사용)

  • 변수임

 

실행 결과를 변수로 저장

: backquote(`) 또는 $( )로 둘러싸기

date_str=`date +"%Y%m%d"`     
//            -> 날짜를 오른쪽 형식으로
date_str=$(date +"%Y%m%d")

 

명령 문자열을 실행하기

eval 명령문자열

  • cmd="ls –alF"
  • eval "$cmd

 

환경 변수

  • 환경(environment)
  • 프로세스를 둘러싼 주변 정보
  • env
  • 상속가능
  • export var1 = 2


exporting

  • sub-shell (쉘 안의 쉘)에서 상속받아 사용할 수 있도록 변수를 공개
  • 사용자 변수를 환경 변수로 전환
  • export나 declare –x로 지정

 

권한

u/g/o

chmod u+x test1.sh    
// -> owner에게 실행권한을 주기

실행 시 경로를 주거나 어떤걸로 실행할지 알려주면 됨

./test.sh
bash test.sh

 

소유자 변경

chown 아이디 파일

 

Boolean expression


&&, ||, !, –a, –o 

  • -a = &&이고 -o = ||

(( 0 && 1 )) && echo true || echo false   

  • -> then else
  • ((식)) && 참이면 실행 || 거짓이면 실행

(( 0 || 1)) && echo true || echo false

  • integer
    -eq -ne -gt -ge -lt –le

  • string
    < <= > >= = != -z –n
  • -z : zero length?
  • -n : null?

 

if

세미콜론, 공백과의 싸움

if [ 조건 ]; then
	실행문
fi

한 줄 쓰기

x=3; if [ $x –eq 3 ]; then echo "x is 3"; fi

 

주의사항

  • -eq 연산자는 정수 연산자
  • = 연산자는 문자열 연산자
  • 변수가 정의되지 않았거나 공백을 포함할 수 있으므로 quotation 필요
x="he llo"
if [ "$x" = "he llo" ]; then echo "$x"; fi

 

  • ==는 shell마다 약간 호환성 차이가 있음, 하나(=)를 써라

 

for/while

for loop은 foreach 형식만 지원

for(int i = 0; i <10; i++)의 형식은 while문으로 작성해야 함

while [ 조건 ]; do
	실행문
done
i=3; while [ "$i" -lt 10 ]; do echo "$i"; i=$((i+1)); done
3
4
5
6
7
8
9

무한루프 :

while :; do
	date
	sleep 5
done

or watch 명령어로 주기적으로 감시 가능

watch -n 5 "date"

for

for 변수명 in 리스트; do
	실행문
done

 

for dir in *; do
//* : 현재 경로의 파일/디렉토리를 dir에 바인딩
	[ -d "$dir" ] && (echo -n "$dir   "; cd "$dir"; ls | wc –l)
    //[ ] 가 참이면 ()를 실행
    // -d dir이 디렉토리냐?
    //에코쓰고 이동하고 안에 있는 파일의 갯수를 찍어(현재 디렉토리 하위 디렉토리 안의 파일 갯수 확인)
    //서브쉘이 끝나면 원래 디렉토리로 돌아옴; 돌아오는 코드 귀찮아서 서브쉘 
    //서브쉘 중간에 실패 시 자동으로 오니까..
done

swtich-case 문

case 변수 in 
케이스1)
	실행문;; 
케이스2) 
	실행문;;
*)
	실행문;;
esac

 

입출력 리다이렉트

 

1(생략) 이거 아니고 0(생략) 이거임! 0번이 표준입력

합칠 수도 있다!

 

필터

프로세서의 출력을 다른 프로세스의 입력으로 전달

  • | 파이프 기호를 이용
  • ls | head : ls의 출력을 head의 입력으로 받아

line by line으로 세서 출현 빈도 수로 정렬

cat test.txt | sort | uniq –c | sort -n
  • sort 알파벳순 정렬
  • uniq -c 중복을 없애면서 중복 갯수를 출력(카운트 옵션)
  • sort -nr  맨 앞을 숫자로 보겠다, 출현 빈도수가 높은걸 맨 앞으로 두고 나머지를 두라(r옵션이 desc)
find /etc 2>&1 | grep 허가
//찾아 여기서 에러랑 아닌거 같이 모아서 허가 찾아

 

문서 보기

cat은 페이징 없음

more/less 페이지씩 보여줌 엔터치면 더 보여줌(pager)

grep 문자열 파일

grep -r (모든 파일에 대해 recursive 하게 검색)

grep bash .bashrc | less -eMR

head 앞에 몇 줄만

tail 뒤에 몇 줄만 tail -f 최근 접속 실시간 출력

 

잘라내서 쓰기

cut –d 구분자 –f 필드번호 파일

구분자로 split해서 시작번호는 1번부터

1,3,4,7번째를 모아서 :로 붙여서 보여줌

grep irteam /etc/passwd | cut –d: –f1,3-4,7

 

shebang(#!)쉐벵

  • 어떤 인터프리터 프로그램을 사용할 것인가를 지정
  • #!/bin/bash
  • #!/usr/bin/env python
    • 권장하는 방식
    • 지정해주면 파이선으로 실행하지 않고 배쉬로 실행해도 배쉬가 첫 줄을 보고(인터프리터) 파이선인지 파악하여 파이선으로 실행 함
    • 리눅스 포직스(?)

 

commane line arguments

$0 자기자신 쉘 이름

$# 변수 몇 개?

$? 실행 결과 0:정상; 0이 아니면 에러

 

설정

Bourne shell

  • ~/.profile

Bash

  • login shell
  • bashrc
  • non-login shell
  • bash_profile -> 이거 하나 만 만들고 그 안에서 rc를 불러오도록 하는게 좋음

바로 적용(source)

source ~/.bashrc

 

find

find 디렉토리 목록 –name 이름패턴 –type 파일유형

find /usr/lib64
find /usr/lib64 –name "lib*.a" –ls
find /usr/lib64 –type d –name "py*"
find /usr/lib64 \( –name "python*" –o –name "pgsql*" \) –ls

 


쉘 문법 체크: shellcheck

컴퓨터에 깔아서 쓸 수도 있고, 인텔리제이 플러그인으로도 있다고 함

https://www.shellcheck.net/

 

ShellCheck – shell script analysis tool

ShellCheck finds bugs in your shell scripts

www.shellcheck.net

 

728x90
반응형

'개발 > shell' 카테고리의 다른 글

[shell] return & exit  (2) 2024.09.25
[shell] > 와 & 에 대한 고찰  (0) 2022.01.11
반응형
  • 인스턴스 여러개 띄운거 -> multi-process
  • 한 인스턴스 안에서 동시 요청 -> multi-thread
    • controller는 스프링에 의해 자동 multi thread(여러 요청이 동시에 들어올 경우)
    • 서비스 로직을 multi-thread로 구현한다는 것은 보통 비동기로 구현하는 것을 의미

 

multi-thread파악

 

11번 우르르쾅쾅 요청하기

curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums & curl -v http://localhost:5000/api/setting/enums

 

서버 설정

스래드 맥스를 5로 잡았다. 

springboot 2.7기준

server.tomcat.threads.max=5
server.tomcat.threads.min-spare=1

10초 딜레이를 넣은 api를 요청했다.

 

결과

5-5-1 순으로 쌓였다가 순차 실행한다.

2023-04-25 07:25:52 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-4] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:25:52 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-2] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:25:52 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-5] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:25:52 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-1] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:25:52 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-3] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

---> 응답 다 받고

2023-04-25 07:26:02 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-2] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:26:02 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-4] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:26:02 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-3] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:26:02 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-5] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

2023-04-25 07:26:02 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-1] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  - 

---> 응답 다 받고

2023-04-25 07:26:12 INFO  [c.n.d.common.filter.RequestLogFilter    .doFilter            :  36][http-nio-5000-exec-1] [REQUEST] [GET] /api/setting/enums
Headers : {host=localhost:5000, user-agent=curl/7.64.1, accept=*/*}
Request body :  -

 

다른 방법:

postman > runner > iteration 을 이용한 run도 응답을 다 받을 때 까지 기다림

https://stackoverflow.com/questions/36157105/postman-how-to-make-multiple-requests-at-the-same-time

 

Postman: How to make multiple requests at the same time

I want to POST data from Postman Google Chrome extension. I want to make 10 requests with different data and it should be at the same time. Is it possible to do such in Postman? If yes, can any...

stackoverflow.com


참고

브라우저로는 테스트가 힘들다..

탭으로 여러개 띄우면 안되고, 브라우저 자체를 여러개 해야하는데 동시 요청이 사실상 힘듦..

정 탭으로 해야하면 url 뒤에 fragment를 다르게 요청하면 된다고 한다.

  • In Chrome, the calls get queued when you call the same resource from two tabs, but executed in parallel when you make the calls from different windows.
  • In IE11, they always get executed in parallel
  • In Firefox, it doesn't matter whether the calls are from different tabs or windows, they always get queued.

All of them execute them in parallel when the URLs are slightly different, by adding a different fragment or parameter.

서버 설정이 1이면 당연히 순차겠지..

https://stackoverflow.com/questions/19774193/subsequent-rest-call-is-blocked-until-previous-one-is-finished

 

Subsequent REST call is blocked until previous one is finished

I Have REST service @Path("/rest") @Component public class MyRestService { @Inject private MyBean bean; @GET @Path("/do") public String start() { this.logger.info("

stackoverflow.com

 

728x90
반응형
반응형

유지보수하기 좋은 코드를 구현하는 개발 문화 어떻게 만들 것인가? by 박재성님

 

1. 변화를 어떻게 가져오는가

  • 의지력이 아닌 환경. 환경(상황)을 바꿔라
    • 퇴근 후 다른 곳(스터디 카페)으로 퇴근한다
    • 티비를 버린다, 폰을 버린다. 앱을 삭제한다.. 등
  • 책임감
    • 삶이 앞으로 나아가는데 필요한 견인력

 

2. 의식적인 연습

  • 그냥 한다고 다 되는건 아닌건 아니다. 목적이 있는, 의식이 있는 연습이 필요하다.
  • comfort zone을 벗어난 지점에서 진행, 자신의 현재 능력을 살짝 넘어가는 작업을 지속적으로 시도
  • 명확하고 구체적인 목표를 가지고..
  • 신중하고 계획적으로, 온전히 집중하고 의식적으로 행동할 것을 요구
  • 피드백과 피드백에 따른 행동 변경

2-1. 요즘 제일 비싼 비용은? 인건비!! => 가독성이 제일 중요; 읽기 좋은 코드

  • 인덴트를 줄이고
  • else를 없애고
  • stream을 써보고
  • 메서드 분리! 

 

  • 모든 원시값과 문자열을 포장한다.
  • 일급 콜렉션
  • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 함수의 인자 수를 줄인다. 3개 이상은 가급적 피한다.
  • 클래스 분리

 

3. 리팩하려면 테스트 코드가 기반이 되어야 함

 

4. 개인 -> 동료로 영향력 확대 => 힘든게 당연하다.

변화를 만들려면 리더쉽을 발휘하고 감정 노동을 해야한다.

리더쉽 / 감정 노동은 AI 시대에 가장 필요한 역량이다...ㅋ

- 사람은 변화를 거부해

- 팀은 더 거부해

- 대부분의 사람들은 변화에 실패해봤어

 

4-1. 시작

묵묵히 혼자 진행!! 내가 맡은 기능에는 적용. 나를 위해서

-> 관심있는 사람이 생기면 전파

-> 작은 성공 경험

 

4-2. 리팩기준(레이어드 아키텍처)

서비스의 로직을 도메인으로...

-> TDD하기 쉬움

- 서비스 : 도메인 객체 생성, 메세지 보내는 역할, 외부에 전달 only

- 도메인에 로직 구현하면 mock할게 없으니까 테스트가 쉽다는거임..

 

5. 리더/시니어로서 만들고 싶은 개발 문화

거창한 포부! 야 우리 같이 성장하쟈???

* 아웃사이드 인 접근 방식: 외부에, 과거에 좋았다고 한 것들을 그대로 도입(탑다운)

-> 실패의 지름길

 

* 인사이드 아웃 접근 방식: 팀원들의 의견에 의한 변화; 시간이 오래걸림(바텀업)

  • 리더가 하자고하면 싫어함(뭘 듣고 왔다냐)
  • 팀원들은 침묵을 선택한다. 나한테 이득이 어딧나
  • 팀원들 말문이 트어야 한다.
  • 어떤 의견을 제기해도 벌을 받거나 보복당하지 않을 거라고 믿는 조직
  • 팀원들과의 신뢰 형성이 우선
    • 1:1 면담
      • 어떻게 하면 될까? 너라면 어떻게 할래? -> 해답을 스스로 제시하도록
    • 잘 듣고 힘들겟구나 공감하고, 반문
    • 이 중 우선순위 높고, 가장 효과있을 것 하나 골라서 시작, 익숙해질 때 까지 집중
    • 하나만 성공하도록 -> 성공 경험이 중요; 한번에 한 가지에 집중 -> 작은 성공

> 도서 "두려움 없는 조직" 추천

- 심리적 안정감! 이 필요


정리

  • 새로운 문화를 정착하기 위해 가장 중요한 것은 리더의 인내심의 용기
  • 새로운 문화를 만들면서 초기 학습 비용 등으로 인해 생산성 저하
  • 안정화하는데 최소 1년 이상의 시간을 투자해야 한다는 마음으로 믿고 기다려야 함
  • 현재보다 조금씩 나아지고 있다는 방향성이 중요
    • 중요한 것은 지금 어떤 practice를 적용하는가가 아님
  • 문화를 만들고 변화를 만드는 일은 리더만의 책임이 아니다.
  • 누구도 대체할 수 없는 존재가 되고 싶으면 지금 당장 도전
  • 좋은 회사는 실패해도 같이 도전하는 사람을 원한다.
  • 실패의 책임을 묻는다면 그만둔다. 굳

 

사람에 대한 존중, 신뢰, 심리적 안정감이 기반되어야 한다.

728x90
반응형

'architecture > knowledge' 카테고리의 다른 글

[http] http 기본 지식  (0) 2022.05.19
웹브라우저 요청 흐름  (0) 2022.05.19
[webwork] struts? webwork? xwork?  (0) 2022.01.17
반응형

1부 소개


1장 설계와 아키텍처란?

1. 설계(design)와 아키텍처(architecture) 차이: 없다.

  • 아키텍처: 저수준의 세부사항과는 분리된 고수준의 무언가를 가리킬 때
  • 설계: 저수준의 구조 또는결정사항 등을 의미

-> 저수준(세부사항) & 고수준(구조) 모두 소프트웨어 전체 설계의 구성요소; 개별로는 존재할 수 없고 경계도 뚜렷하지 않다.

2. 목표: 필요한 시스템을 만들고 유지보수하는데 투입되는 인력의 최소화

3. 함정: 지저분한 코드를 작성하면 단기간에는 더 빠르게 갈 수있고 생산성 낮아진다?

-> 빨리 가는 유일한 방법은 제대로 가는것이다.

결론

앞으로 클린 아키텍처가 무엇인지 공부해서 비용은 최소화, 생산성은 최대화 할 시스템을 만들자.


2장 두 가지 가치에 대한 이야기

1. 개발자가 높게 유지해야하는 두 가지 가치

  • 행위(behavior)
    • 요구사항 만족하도록 코드를 작성하는것
  • 구조(structure)/아키텍처
    • 변경하기 쉬워야
      • 변경사항을 적용하는데 드는 어려움은 변경되는 범위(scope)에 비례해야하며 변경사항의 형태(shape)와는 관련이 없어야 한다.
    • 아키텍처는 형태에 독립적이어야

-> 보통 행위에만 초점을 두지만, 둘 다 중요하다는 것.

2. 더 높은 가치란?

-> 구조

  • 완벽하게 동작하지만 수정이 아예 불가능한 프로그램 / 변경 비용이 창출비용을 앞서는 프로그램 -> 유지보수 불가능 -> 곧 쓰레기 (x)
  • 동작하지 않지만 변경 쉬운 프로그램 -> 동작하도록 수정 가능, 요구사항 변경시 유지보수 가능 (v)

결론

기능을 개발하기 쉽고, 간편하게 수정할 수 있으며, 확장하기 쉬운 아키텍처를 만들어야 한다.

이를 위해 투쟁하는 것이 곧 개발자의 책임

728x90
반응형
반응형

서로 다른 레이어에서 변수들을 전달할 때 서로 다른 dto를 사용하는데, 얼핏 비슷하면서도 한두 개 다른 변수들을 하나씩 매핑해 주는 게 매우 귀찮고 번거로웠다.

어휴..

그래서 이를 자동(?)으로 해주는 라이브러리 같은 게 있을까 싶어서 몇 개 찾아보았다.

구글링 해보면 여러 개가 나오는데 아래 3개를 먼저 확인해 보았다.

1. 스프링에 기본 내장되어 있는 BeanUtils.copyProperties

2. 사용하기 편해 보이는 ModelMapper

3. 요즘 제일 인기 있는 Mapstruct

 

실제로 사용하려는 목적이기에 현실적인 사용성을 중심으로 살펴보았다.

  dependency di주입 원리(setter 선언 필요?) 필드 커스텀 가능 여부
Mapstruct 추가 필요(4개) 빈 주입 필요
interface 작성
리플랙션X 
- 컴파일 시 작성된 getter/setter or 빌더 or 생성자 등 관련 코드를 이용하여 변환 class 만듦
- setter 없어도 됨
매핑으로 커스텀 설정 가능
ModelMapper 추가 필요(1개) 주입 필요 없음 리플렉션O getter/setter 기반
- 필드명/타입이 동일할 경우 필드 엑서스 레벨을 private으로 설정하면 setter가 필요없지만
- 커스텀 시 setter 선언 필요
커스텀을 위해 typeMap과 addMapping 을 이용하여 손수 매핑해줘야 함
BeanUtils.copyProperties 추가 필요 없음
spring 내장
주입 필요 없음
static method
void type
리플렉션O getter/setter 기반
- setter 선언 필요
- 커스텀 시 setter 수정 필요
더 복잡한 변환이 필요할 경우 사용 불가하며 spring BeanWrapper를 직접 구현해야 함

Mapstruct

lombok과 함께 사용가능(lombok compile 이후에 작동)

implementation 'org.mapstruct:mapstruct:1.5.3.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'
//If you are using Lombok 1.18.16 or newer you also need to add lombok-mapstruct-binding in order to make Lombok and MapStruct work together.
implementation 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
@Mapper(config = CommonMapper.class,
        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface CharacterUpdateRequestMapper {

  @Mapping(target = "searchType", source = "findType")
  @Mapping(target = "searchValue", source = "value")
  @Mapping(target = "characterDetail", source = "detailCharacterData")
  CharacterUpdateRequest toRequest(ExternalCharacterUpdateRequest external);

  @Mapping(target = "position", source = "lastSavePosition")
  @Mapping(target = "openedDivisions", ignore = true)
  @Mapping(target = "divisionFlattened", ignore = true)
  CharacterDetail toDetail(ExternalCharacterDetail external);
}
@RequiredArgsConstructor
...
private final CharacterUpdateRequestMapper characterUpdateRequestMapper;

public CharacterUpdateRequest usingMapStruct() {
  return characterUpdateRequestMapper.toRequest(externalCharacterUpdateRequest);
}

필드명이 다를 경우만 mapping을 적어주면 된다(nested object도. 을 사용하여 매핑 가능).

class type이 다르면 converter를 별도로 구현해야 한다.

 

컴파일 시 구현체를 자동으로 생성해 주며 잘못된 매핑을 적을 경우 컴파일이 되지 않기 때문에 런타임 에러를 방지할 수 있다.

참고자료

https://mapstruct.org/documentation/stable/reference/html/#defining-mapper

https://madplay.github.io/post/mapstruct-in-springboot


ModelMapper

implementation 'org.modelmapper:modelmapper:3.1.1'
public class ModelMapperUtils {

  private static final ModelMapper MODEL_MAPPER;

  static {
    MODEL_MAPPER = new ModelMapper();
    MODEL_MAPPER.getConfiguration()
        .setMatchingStrategy(MatchingStrategies.STRICT)
        .setFieldAccessLevel(AccessLevel.PRIVATE)// 필드 전체 이름이 같아서 setter가 필요없다면
        .setFieldMatchingEnabled(true);
  }

  public static CharacterUpdateRequest fromExternalCharacterUpdateRequest(ExternalCharacterUpdateRequest externalCharacterUpdateRequest) {
    //클래스 매핑하고 필드 매핑해야함
    MODEL_MAPPER.createTypeMap(ExternalCharacterUpdateRequest.class, CharacterUpdateRequest.class)
        .addMapping(ExternalCharacterUpdateRequest::getDetailCharacterData, CharacterUpdateRequest::setCharacterDetail)
        .addMapping(ExternalCharacterUpdateRequest::getFindType, CharacterUpdateRequest::setSearchType);
    MODEL_MAPPER.createTypeMap(ExternalCharacterDetail.class, CharacterDetail.class)
        .addMapping(ExternalCharacterDetail::getLastSavePosition, CharacterDetail::setPosition);
    return MODEL_MAPPER.map(externalCharacterUpdateRequest, CharacterUpdateRequest.class);
  }
}

같은 형/타입일 경우 자동으로 매핑해 주고, 변형된 필드일 경우 손수 getter/setter를 사용하여 매핑해줘야 한다.

런타임 시점에 Reflection API를 사용하여 객체를 매핑하기 때문에 컴파일 시점에 성능 최적화를 하지 못하고 다른 방식보다 오버헤드가 많다는 특징이 있다.

참고자료

https://stackoverflow.com/questions/70274381/how-to-map-a-nested-list-using-modelmapper

https://www.baeldung.com/java-modelmapper


BeanUtils.copyProperties

예전에 다른 분께 memory leak 이슈가 있으니 사용에 조심해야 한다고 피드백받았던 적이 있어서 잠시 사용을 안 했었는데, 다시 찾아보니 관련 reference가 잘 나오지 않는다..

1. Circular References (순환 참조)

  • 객체 간에 순환 참조가 있는 경우, 객체가 서로를 참조하게 되어 가비지 컬렉션이 되지 않을 수 있습니다. 이로 인해 메모리 누수가 발생할 수 있습니다.

2. Large Object Graphs

  • 복사하는 객체가 큰 그래프를 가지거나 깊게 중첩된 경우, 복사 과정에서 많은 메모리를 소모할 수 있습니다. 특히, 여러 번 복사할 경우, 사용하지 않는 객체가 메모리에 남아 있을 수 있습니다.

3. Static Fields

  • 복사하려는 객체가 정적 필드를 포함하고 있다면, 이 필드는 여전히 메모리에 남아 있어 메모리 누수가 발생할 수 있습니다. 정적 필드는 클래스 로딩 시 한 번만 메모리에 할당됩니다.

 

dto 내부에서 생성자 및 세팅 함수 작성 시 유리하다.

public CharacterUpdateRequest(ExternalCharacterUpdateRequest externalCharacterUpdateRequest) {
  BeanUtils.copyProperties(externalCharacterUpdateRequest, this);
  super.setSearchValue("custom setting");
}

같은 형/타입일 경우 자동으로 매핑해 주고, 그 외에는 setter 이용하는 등 개발해줘야 한다.

 

변형폼:

//일부만 변환할 때
public static void copyProperties(Object source, Object target, Class<?> editable)

//일부만 제외할 때
public static void copyProperties(Object source, Object target, String... ignoreProperties)

시작은 mapStruct 말고 modelMapper를 쓰려고 시작한 서베이인데,
1. 리플렉션 방식은 필드 개수가 많아지면 성능이 구려짐
2. 결국 getter/setter를 통해 일대일 매핑
3. 가독성이 mapStruct가 훨씬 좋음
위의 이유로 인해 mapStruct 쓰는 게 나을 것 같다는 생각을 하였다.

mapStruct 사용후기

before: 아래와 같은 함수를 2쌍 만들어 주었어야 함

  public static OasisBannerResponse from(StaticOasisBanner entity) {
  return OasisBannerResponse.builder()
      .seq(entity.getSeq())
      .exposureType(entity.getExpsType())
      .regDate(entity.getRegDate())
      .startDate(entity.getStartDate())
      .endDate(entity.getEndDate())
      .linkType(CommonEnumUtil.fromCode(entity.getLinkType().toString(), LinkType.values()).getCode())
      .linkUrl(entity.getLinkUrl())
      .iconImageUrl(entity.getIconImgUrl())
      .backgroundColor(entity.getBackgroundColor())
      .koreanText(entity.getKoText())
      .englishText(entity.getEnText())
      .registerer(entity.getRegisterer())
      .updater(entity.getUpdater())
      .build();
}

after: 인터페이스에 변수 매핑하면 끝..

object -> entity 한방향 매핑만 잘 연결해 놓으면 역방향 매핑(entity -> object)은 @InheritInverseConfiguration만 달아주면 알아서 매핑해준다는게 너무 편리하였다.

@Mapper(config = CommonMapper.class,
        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface OasisBannerMapper {

  @Mapping(target = "exposureType", source = "expsType")
  @Mapping(target = "iconImageUrl", source = "iconImgUrl")
  @Mapping(target = "koreanText", source = "koText")
  @Mapping(target = "englishText", source = "enText")
  @Mapping(target = "linkType", expression = "java(convertLinkType(entity.getLinkType()))")
  OasisBannerResponse toResponse(StaticOasisBanner entity);


  @Mapping(target = "extra", ignore = true)
  @Mapping(target = "lastDate", ignore = true)
  @InheritInverseConfiguration
  StaticOasisBanner toEntity(OasisBannerResponse request);

 

그러나...

단점 1. 커스텀이 (생각보다) 쉽지 않다(러닝 커브가 있음)

적절한 설정값을 확인해야 하고 다양한 매핑을 지원해야 하는지 확인해야 하는 등, 특이점이 많은 매핑에는 아직 시간이 걸린다(가끔은 그냥 빌더가 더 편하다).

예시로..

  @Mapping(target = "expsType", source = "request.exposureType")
  @Mapping(target = "iconImgUrl", source = "request.iconImageUrl")
  @Mapping(target = "koText", source = "request.koreanText")
  @Mapping(target = "enText", source = "request.englishText")
  @Mapping(target = "extra", ignore = true)
  @Mapping(target = "lastDate", ignore = true)
  @Mapping(target = "updater", source = "updater")
//  @InheritInverseConfiguration
  StaticOasisBanner toEntity(OasisBannerResponse request, String updater);

위처럼 argument가 여러 개일 경우 @InheritInverseConfiguration를 사용하지 못하고 하나씩 다 매핑해줘야 한다.(이걸로 못 찾고 시간을 허비함..)

@InheritInverseConfiguration를 사용하여 반대 매핑도 자동으로 되는 게 매력적이었는데, 다시 수동으로 하려니 메리트가 절감된 느낌

 

단점 2. 에러가 친절하지 못함

단점 1에 기술한대로 추가 arguments가 있을 때는 모두 매핑해야 한다는 사실을 모르고 처음에는 생각대로 해봤는데, 빌드조차 되지 않았다. 그런데 에러 내용이 갑자기 querydsl 관련 에러였다?

처음에는 빌드창을 조금만 띄워놓았어서 에러내용을 끝까지 못 보고 querydsl 설정이 꼬였나 싶어서 거기만 팠는데,, 에러창을 더 열어보니 결국은 mapstruct 빌드로 부터 시작한 것이었다.

파악해 보니, 빌드 순서가 lombok -> mapstruct -> querydsl 순이었고

mapstruct 빌드가 실패하면 querydsl도 빌드되지 않기 때문에 에러메시지는 querydsl이 실패한 것처럼 보이나,

실제로 에러의 시작점은 mapstruct이었던 것이다(처음에 이것을 모르고 querydsl 설정이 문제라고 생각하여 시간을 허비함).

 

단점 3. @MappingTarget사용 시 setter 사용

@InheritConfiguration
void setEntity(OasisBannerResponse request, @MappingTarget StaticOasisBanner entity);

위처럼 @MappingTarget을 이용하면 setter 기반으로 매핑을 만들어주기 때문에 target 객체에 setter를 선언해야 한다..

setter를 최소화하고 싶었는데.... 고민이 더 필요할 것 같다.

기존 <interface>

@Mapping(target = "exposureType", source = "expsType")
@Mapping(target = "iconImageUrl", source = "iconImgUrl")
@Mapping(target = "koreanText", source = "koText")
@Mapping(target = "englishText", source = "enText")
@Mapping(target = "linkType", source = "linkType", qualifiedByName = "convertToLinkTypeFrom")
OasisBannerResponse toResponse(StaticOasisBanner entity);

-> 구현체: Builder를 이용한 객체 생성(builder -> constructor -> getter/setter 순으로 찾아서 구현체 생성)

@Override
public OasisBannerResponse toResponse(StaticOasisBanner entity) {
    if ( entity == null ) {
        return null;
    }

    OasisBannerResponse.OasisBannerResponseBuilder oasisBannerResponse = OasisBannerResponse.builder();

    if ( entity.getExpsType() != null ) {
        oasisBannerResponse.exposureType( entity.getExpsType() );
    }
    ... 생략

 

<interface> MappingTarget 사용 시

 

@InheritConfiguration
void setEntity(OasisBannerRequest request, @MappingTarget StaticOasisBanner entity);

 

-> 구현체: setter를 이용한 매핑

@Override
public void setEntity(OasisBannerRequest request, StaticOasisBanner entity) {
    if ( request == null ) {
        return;
    }

    entity.setExpsType( request.getExposureType() );
    if ( request.getIconImageUrl() != null ) {
        entity.setIconImgUrl( request.getIconImageUrl() );
    }
    
    ... 생략

 

단점 4. lombok의 @Singular와 함께 사용 불가한 듯

<before> 기존에는 아래처럼 구현했었음

public class OasisBannerResponse {

    @JsonInclude(value = JsonInclude.Include.NON_DEFAULT)
    @JsonProperty("freeWayName")
    @Singular
    private List<CodeValueModel> freeWayNames;

... 변수 생략

    public static OasisBannerResponse from(StaticOasisBanner entity) {
      return OasisBannerResponse.builder()
          .startDate(entity.getStartDate())
          .endDate(entity.getEndDate())
          .linkType(entity.getLinkType())
          .linkUrl(entity.getLinkUrl())
          .iconImageUrl(entity.getIconImgUrl())
          .backgroundColor(entity.getBackgroundColor())
          .freeWayName(new CodeValueModel(LanguageType.KO.getCode(), entity.getKoText()))
          .freeWayName(new CodeValueModel(LanguageType.EN.getCode(), entity.getEnText()))
          .build();
	}
}

<after> mapStruct 사용

기댓값: singular처럼 매핑 -> but 컴파일 에러 남

  @Mapping(target = "iconImageUrl", source = "iconImgUrl")
  @Mapping(target = "freeWayName", source = "enText", qualifiedByName = "addEnglish")
  @Mapping(target = "freeWayName", source = "koText", qualifiedByName = "addKorean")
  OasisBannerResponse toResponse(StaticOasisBanner entity);

  @Named("addKorean")
  default CodeValueModel addKorean(String koText) {
    return toCodeValueModel(LanguageType.KO, koText);
  }

  @Named("addEnglish")
  default CodeValueModel addEnglish(String enText) {
    return toCodeValueModel(LanguageType.EN, enText);
  }
error: Target property "freeWayName" must not be mapped more than once.

error: Unmapped target property: "freeWayNames".

여러 개 있어서 싫고,,매핑을 안 해서 싫더냐?! 거참 까다로운 자식..

해결: 결국.. entity를 다 받아서 했다..ㅠ

@Mapping(target = "iconImageUrl", source = "iconImgUrl")
@Mapping(target = "freeWayNames", source = ".", qualifiedByName = "setFreeWayNames")
OasisBannerResponse toResponse(StaticOasisBanner entity);

@Named("setFreeWayNames")
default List<CodeValueModel> setFreeWayNames(StaticOasisBanner entity) {
  List<CodeValueModel> codes = new ArrayList<>();
  setLanguageText(LanguageType.KO, entity.getKoText(), codes);
  setLanguageText(LanguageType.EN, entity.getEnText(), codes);
  return codes;
}

그리고 

@Singular
private List<CodeValueModel> freeWayNames;

@Singular 선언이 있으면 위처럼 freeWayNames에 매핑을 했음에도 freeWayName도 매핑하라는 에러가 나고, 결국 @Singular를 지워야만 한다.

error: Unmapped target property: "freeWayName".

즉, 두개를 동시에 못쓴다는 이야기..


참고

1. 함수를 통해 변수를 변환하고 매핑해야 하는 경우

여러 가지 방법이 있겠지만 expression = "java()" 이 방법은 코드를 하드코딩하는 방법이라 잘못된 점을 빌드할 때까지 알 수 없음..

qualifiedByName = "" 방법을 사용하는 게 더 나은 것 같다.

enum을 더 효율적으로 매핑할 수 있는지 확인 필요

2. 설정을 함수별로 세팅할 수 있음

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
             nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
void update(AmcPackageRequest amcPackageRequest, @MappingTarget AmcPackage amcPackage);

3. MappingConfig 안에서 세부 설정을 할 수 있음

특히 collection mapping관련해서 설정값을 확인할 필요 있음


참고

성능: https://www.baeldung.com/java-performance-mapping-frameworks

사용방법: 

https://medium.com/naver-cloud-platform/%EA%B8%B0%EC%88%A0-%EC%BB%A8%ED%85%90%EC%B8%A0-%EB%AC%B8%EC%9E%90-%EC%95%8C%EB%A6%BC-%EB%B0%9C%EC%86%A1-%EC%84%9C%EB%B9%84%EC%8A%A4-sens%EC%9D%98-mapstruct-%EC%A0%81%EC%9A%A9%EA%B8%B0-8fd2bc2bc33b

https://jiwondev.tistory.com/250

728x90
반응형
반응형

환경: springboot 2.7.3, java11, gradle 7.5

테스트를 돌리는데

분명 이전에 성공했던 테스트인데

시간이 흐른 뒤 재실행했을 때 아래와 같은 에러가 났다.

Execution failed for task ':test'. > No tests found for given includes:

 

그 사이 코드가 바뀐 것도 없어 안될 리가 없을 터.

게다가 다른 클래스의 테스트 코드는 잘 실행되고 한 클래스의 테스트코드만 안돼서 이상했다.

 

구글링 해보면

junit vintage 버전 충돌 어쩌구라고 하는데 나에게는 해당되지 않는 듯하여 과감하게 버리고

https://www.inflearn.com/questions/15495/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EB%8F%84%EC%A4%91-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D

 

테스트 도중 에러 발생 - 인프런 | 질문 & 답변

FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':test'. > No tests found for given includes: [jpabook.jpashop....

www.inflearn.com

여기 문제와 비슷한 것 같아서 설정을 바꿔보았는데도 동일하였다.

 

한 30분 씨름했는데,, 해결은 

invalid cache 날리고 혹시 몰라서 gradle 클린하고 테스트 실행하니까 잘되었다..ㅎㅎㅎㅎ 츠암나..

 

728x90
반응형

+ Recent posts