728x90

문제

<choose>
    <when test = ”isLiked == ‘t’”>
        UPDATE t_book_like
        SET is_liked = 'f'
        WHERE identity_id = #{userId} AND book_id = #{bookId}
    </when>

<when test = ”isLiked == ‘t’”> 여기서 난 문제였다.

 

 

해결

<when test='isLiked == "t"'> 이렇게 ‘ ‘ 로 해주니까 해결 되었다.

728x90

헥사고날 아키텍처를 처음 접해보았을 때는 인턴때였습니다. 프로젝트 베이스가 깔려지고 사수님한테 설명을 듣고 "나도 저런 아키텍처를 내 코드에 사용해보고싶다.. 더 나아가서 아키텍처를 개발해보고 싶다" 라는 생각이 들었습니다. 마침F-lab을 하며 멘토님이 헥사고날 아키텍처를 사용하여 코드 작성하는 모습을 보여주었습니다. 인턴 때 예습이 되어있어서 그런지 정말정말 이해가 잘 되는 느낌이었습니다. 

 

 

 

헥사고날 아키텍처

  • 내부 영역 - 순수한 비즈니스 로직을 표현하며 캡슐화된 영역이고 기능적 요구사항에 따라 먼저 설계
  • 외부 영역 - 내부 영역에서 기술을 분리하여 구성한 영역이고 내부 영역 설계 이후 설계

이상 대략적인 설명이었고

자세한 설명은 https://netflixtechblog.com/ready-for-changes-with-hexagonal-architecture-b315ec967749 여기에 가서 보시길 바랍니다.. 괜히 잘못된 정보가 전해지면 안되니까요..

 

비즈니스 로직에 집중

내/외부를 분리하고 내부에는 비즈니스 로직들만 존재하고 외부에는 내부의 비즈니스 로직의 관심사가 아닌 것들(database, logging, etc...)이 존재하였습니다. 내/외부를 adaptor와 port를 이용하여 내부의 비즈니스 로직이 필요한 것들을 꽂아서 사용할 수 있는 구조였습니다. 다음 그림으로 설명이 될 것 같습니다.

포트(port)

 

어뎁터(adaptor)

USB를 옆의 그림처럼 USB 포트에 꼽아서 사용하는 것과 같다고 생각합니다. USB는 우리가 원하는 것들을 꼽아서 사용하지 않나요? 그런거와 같이 우리가 비즈니스로직에 MySql을 사용하고싶으면 MySql에 해당하는 어뎁터를 포트에 꼽고 H2를 사용하고 싶으면 그에 해당하는 어뎁터를 꼽아 사용할 수 있습니다. 

 

 

 

 

어뎁터는 포트에다가 꽂을 수 있는 것들을 골라서 꼽을 수 있습니다. 비즈니스 로직에 사용하고 싶은 어뎁터로 포트에 꼽으면 되는 구조였습니다.

 

 

 

 

 

 

 

 

 

어떻게?

인터페이스가 가능하게 해줍니다. 

 

원래 이런 구조였습니다. CommandHandler가 바로 MybatisRepositoryRepository를 의존하고 있었습니다. 그런데 사실상 CommandHandler는 MyBatis, JPA, JDBCtemplate 등 무슨 ORM을 사용하는지 비즈니스 로직의 관심사가 아닙니다. 그리고 바로 구현 객체를 의존하면 변경에 유연하지 않다고 생각합니다. 이런 상황을 풀어줄 친구는 인터페이스 입니다. 다음 사진으로 가보겠습니다.

 

이렇게 인터페이스인 IBookRepository를 의존하는 방법입니다. 여기서 인터페이스는 port 역할을 합니다. 그리고 BookMybatisRepository는 adaptor 역할을 합니다.

 확실하게 관심사가 분리될 수 있었습니다. 비즈니스 로직은 비즈니스로직이 중심이 되고 비즈니스 로직이 필요한 것들은 포트를 게이트로 삼아 끼워넣는 방식은 변경에 유연하다고 생각했습니다. 

 

 

 

 

프로젝트 구조 바뀌기 전 후

구조 바뀌기 전
구조 바뀐 후

 

 

훨씬 깔끔해졌습니다..!!!!

 

https://github.com/Taewoongjung/Doseoro_Java/pull/37

 

728x90

이와 같이 패키지 전체를 포인트 컷으로 잡아서 구현한 AOP를 Custom AOP로 바꾸어보았습니다.  

@Aspect
@Component
public class LogAspect {
    private static final Loggerlogger= LoggerFactory.getLogger(LogAspect.class);

    @Around("within(com.myproject.doseoro..*)")
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {

        String params = getRequestParams();

        long startAt = System.currentTimeMillis();

logger.info("----------> REQUEST : {}({}) = {}", pjp.getSignature().getDeclaringTypeName(),
                pjp.getSignature().getName(), params);

        Object result = pjp.proceed();

        long endAt = System.currentTimeMillis();

logger.info("----------> RESPONSE : {}({}) = {} ({}ms)", pjp.getSignature().getDeclaringTypeName(),
                pjp.getSignature().getName(), result, endAt-startAt);

        return result;
    }

    private String getRequestParams() {

        String params = "";

        RequestAttributes requestAttribute = RequestContextHolder.getRequestAttributes();

        if (requestAttribute != null) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                    .getRequestAttributes()).getRequest();

            Map<String, String[]> paramMap = request.getParameterMap();

            if (!paramMap.isEmpty()) {
                params = " [" + paramMapToString(paramMap) + "]";
            }
        }
        return params;
    }

    private String paramMapToString(Map<String, String[]> paramMap) {
        StringJoiner sj = new StringJoiner(",", "[", "]");
        return paramMap.entrySet().stream()
                .map(entry -> String.format("$s -> ($s)",
                        entry.getKey(), sj,join(entry.getValue())))
                .collect(Collectors.joining(", "));
    }
}

 

바꾼 이유는 공부 목적도 있었고 멘토님이 말하시길 "코드상 눈에 보이는 표식을 하지 않았기 때문에 모르는 사람이 볼때는 Aspect 로직이 왜 실행되는지 이해를 하지 못하는 경우도 많다"고 하셨다. 듣고 보니 정말 그럴 것 같았다.

 

어떻게 바뀌었냐면 

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {}
@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Around("@annotation(Logging)") // Custom AOP
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {
    
    	// ... 이하 위 로직과 같음

 

이렇게 바꿈으로써 전에 없었던 어노테이션이 생겼고, 그 어노테이션으로 무엇을 하려고 하는지 정확히 알 수 있었습니다.

 

 

이 과정에서 @Transactional, @Cacheable, @Async 에너테이션도 AOP가 적용된 사례라는 것을 알게되었습니다.

 

 

참고

https://velog.io/@ann0905/AOP%EC%99%80-Transactional%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

 

AOP와 @Transactional의 동작 원리

오늘은 @Transactional의 동작 원리를 AOP와 함께 좀 더 자세하게 조사해보려고 한다.여기서 다루는 내용은 다음과 같다.AOP란 무엇이며 왜 사용하는가Spring AOP는 왜 프록시를 사용하는가@Transactional은

velog.io

https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/

 

AOP 입문자를 위한 개념 이해하기

이 글은 AOP 개념이 생소한 입문자들을 위한 포스팅입니다. 1. OOP의 한계 image…

tecoble.techcourse.co.kr

https://private-space.tistory.com/98

 

Spring에서 AOP를 구현하는 방법과 Transactional

Spring에서 AOP를 구현하는 방법과 Transactional AOP에 관한 간략한 개념이 필요하다면 다른 글을 참조한다. 이 내용은 공식 문서를 참조하여 작성하였다. Spring Framework Document AOP 구현 방식 Spring에서 A..

private-space.tistory.com

 

+ Recent posts