221016 Mapper class Bean 등록 Issue

Spring bean container


우선 본 포스팅에서 다루고자 하는 에러가 어떤 상황에서 발생하게 되었는지에 대해서 간략하게 설명을 하고, 어떤 접근으로 해결하려고 했는지에 대해서 기술해보려고 한다.

프로젝트 진행

회사에서 진행중인 프로젝트에서 백엔드를 구성하면서 간단한 CRUD 처리의 경우에는 JPA를 사용하고, 복잡한 쿼리 사용의 경우에는 MyBatis를 사용하기로 했다. 여기서 말하는 복잡한 쿼리란 간단한 CRUD 이외의 복잡한 쿼리를 말한다.
그냥 이렇게 하라고 하니깐 하는 게 아니라 이렇게 구성을 하면 어떤 이점이 있는지에 대해서 간단하게 짚고 넘어가도록 하자.

  • Hibernate vs MyBatis

    과거 EJB2로 개발을 하던 당시 Gavin king이라는 사람이 사용자 친화적이지 않은 자바 애플리케이션 개발 방식을 좀 더 사용자 친화적이게 만들고자 개발을 하게 된 것이 바로 그 유명한 Hibernate이다. 이 Hibernate가 점차 인기가 많아지자 자바 진영에서 Gavin king을 영입해서 자바 ORM(Object Relational Mapping) 기술에 대한 표준 명세를 개발하도록 하였는데, 그것이 바로 JPA(Java Persistent API)이다.

    JPA는 ORM을 사용하기 위한 인터페이스를 모아둔 것으로, 이를 구현한 프레임워크 중 대표적으로 Hibernate가 있고, Spring에서는 대부분 Hibernate를 사용하고 있다.
    Spring에서는 JPA를 사용할 때 구현체들을 직접 다루지 않고, 구현체들을 좀 더 쉽게 사용하고자 추상화 시킨 Spring Data JPA라는 모듈을 시용하여 JPA 기술을 다룬다.

    그렇다면, JPA 하나만 사용해서 개발하면 만능일까? 찾아보니 대부분 단순한 CRUD의 처리를 하는 경우에는 JPA만을 사용해도 괜찮다고 한다. 하지만, 복잡한 통계나 정산관련 조회 쿼리가 포함된 경우, MyBatis로 처리하면 좀 더 개발자에게 편하다. 물론 JPA로도 복잡한 집계성 쿼리를 처리하는 것도 가능은 하지만 구현이 쉽지 않기 때문에 MyBatis를 사용하는 것이 낫다고 한다.

    따라서 JPA 또는 MyBatis만 사용하는 것이 아닌, 두 개를 적절히 조합해서 프로젝트를 진행하면 업무적으로 효율이 높아질 수 있다는 결론이 나온다.


  • MyBatis의 사용

    MyBatis는 Mapper를 별도로 구성하며, SqlSession을 직접 사용하는 형태가 아닌 Mapper를 통해 처리한다. 기본적으로 Mapper를 사용하게 되면, Mapping 파일이 자동으로 Mapper의 단위가 되기 때문에 유지보수 및 관리에 용이하다.


Read more

210821 "Zone.js has detected that ZoneAwarePromise (window|global).Promise has been overwritten

디버깅 결과 페이지

이번 포스팅에서는 현재 서비스 중인 Angular web app이 iOS 모바일 단말기에서 정상적으로 동작하지 않는 문제에 대해서 작성해보려고 한다.

[Issue]

브라우저에서 디버깅해본 결과, 위의 ERROR를 확인할 수 있었다.
현재 Promise polyfill과 zone.js가 로드되는 과정에서 문제가 되고 있다. 프로젝트 내에서는 zone.js가 로드된 다음에 Promise polyfill가 로드되고 있지만, 이 문제를 해결하기 위해서는 Promise polyfill이 로드된 다음에 zone.js가 로드되어야 한다.
그 이유는 Zone.js의 ZoneAwarePromise가 다음에 로드된 Promise polyfill에 의해 Overwritten되기 때문이다.

[Solution]

현재 프론트엔드 프로젝트의 src/polyfills.ts의 코드를 보면, import 'zone.js/dist/zone'; statement를 확인 할 수 있다. Promise polyfill 다음으로 zone.js를 로드하기 위해서는 앞서 언급한 statement를 main.js로 이전해서 작성해줘야 한다.

(위의 Solution을 적용한 뒤에 정상적으로 동작을 하는지 확인이 필요)

[Promise polyfills과 zone.js의 역할]

Promise polyfills

비동기 코드의 장점을 살리면서 콜백지옥(callback hell)을 피하기 위해 ES6 표준 spec에 포함되어있는 Promise 패턴을 사용한다. 이 Promise polyfill을 사용해서 Promise가 지원되지 않는 브라우저에서 사용될 수 있도록 해야한다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise

zone.js(Zones)

Zones는 Angular의 Change Detection을 위해 중요한 역할을 한다. Angular에서는 click, change, input, submit과 같은 사용자에 의한 이벤트XMLHttpRequest를 통한 원격 서버로부터의 데이터 취득, setTimeout과 setInterval을 통해 앱의 상태를 변화시키고 있다.
Angular에서는 화면을 업데이트하는데 위와 같은 비동기 처리를 사용하고, 로직이 작성되어있는 ts 파일의 변수가 HTML Template에 바인딩되어 ts파일내의 변수의 값이 업데이트되었을때 Template내의 값도 같이 업데이트된다.

이처럼 별도로 프레임워크에 변화가 일어났다는 작업을 하지 않아도 Angular는 알아서 처리를 해주게 되는데, 이때 중요한 역할을 해주는 것이 바로 Zones이다.
Angular의 Zones의 onTurnDone 이벤트가 발생할때마다 전체 어플리케이션에 대해 Change Detection을 수행하게 되고, onTurnDone 이벤트는 Angular의 NgZone이 발생을 시킨다.

ref. Zones를 이용하면, 해당 Zones(JavaScript Execution context)가 실행되는 시점에서 여러 이벤트들을 hooking할 수 있다.

(자바스크립트의 실행 영역을 직접 나누는 것을 도와주기 때문에 여러가지 이벤트들을 hooking하는 것이 가능)

이러한 변화감지는 Zones 덕분에 가능한 것이다.

이처럼 Zones는 Angular가 DOM을 언제 업데이트해야 하는지 쉽게 발견할 수 있게 도와주는 역할을 한다.

210509 ReferenceError regeneratorRuntime is not defined


NodeJS 프로젝트를 진행하면서 비동기로 작성한 함수를 위한 테스트 코드를 작성하고 실행을 했는데 ReferenceError: regeneratorRuntime is not defined 에러가 발생했다.
이전에 ReactJS 프로젝트를 진행하면서 테스트 코드를 작성했을때도 위와 똑같은 에러가 발생했었는데 이러한 경우에는 아래의 두 방법으로 해결을 할 수 있다.

Solution 1) regenerator-runtime를 설치해서 비동기 함수를 작성한 파일의 최상단에 import 'regenerator-runtime/runtime';를 선언해주면 간단하게 해결할 수 있다.

Solution 2) .babelrc 의 설정을 아래와 같이 업데이트한다.

Using @babel/preset-env without that target set will result in the tests using async failing with ReferenceError: regeneratorRuntime is not defined.

1
2
3
4
5
6
7
8
9
10
11
12
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}

이 regenerator-runtime이 무엇인가 한 번 구글링을 해보니,
regenerator-runtime은 compiled/transpiled 비동기 함수들을 위한 runtime을 지원해주는 녀석이라고 한다.
Babel과 같은 컴파일러는 자바스크립트 syntax에 대해서 최신 문법을 이전 문법으로 변환을 해주지만, runtime에 의존적인 결과 코드(비동기 코드)들은 regenerator-runtime으로부터 지원을 받는다고 한다.

210505 screen.getBy*와 getBy의 차이, findBy*/getBy*/queryBy* method의 차이


테스트 코드를 작성하던 도중에 헷갈렸던 개념들에 대해서 반복학습을 위해 포스팅을 해두려고 한다.

screen.getBy vs getBy

screen을 사용하는 이점은 필요한 query를 추가할 때 더 이상 render 함수로 컴포넌트를 렌더링해서 최신 상태를 유지 하지 않아도 된다는 점이다.
beforeEach()함수 내에서 공통적으로 컴포넌트를 렌더링했다면, 이하의 test 블럭에서는 screen을 통해 렌더링된 컴포넌트를 통해 테스트 코드를 작성할 수 있다.

findBy* / getBy* / queryBy* method의 차이

  • getBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 에러를 반환
    → 존재하지 않는 DOM element를 assert할때에는 try-catch문으로 발생한 에러를 별도의 함수에 담아 tobedefined() matcher로 확인을 해야 한다. 이 경우에는 queryBy*를 사용해서 assert하는 것이 좋다.

  • queryBy*

    조건에 맞는 요소를 찾았을때 query에 일치하는 노드를 반환
    조건에 맞는 요소를 못 찾았을때 null을 반환
    → 존재하지 않는 DOM element를 assert할때 유용하다.

  • findBy*

    조건에 맞는 요소를 찾았을때 resolved Promise 반환
    조건에 맞는 요소를 못 찾았을때 rejected Promise 반환

    기존의 waitForElement()함수는 이제 deprecated되었기 때문에 findBy* 함수를 사용해야 한다.

    1
    2
    3
    4
    5
    6
    test('nowPlaying API method 테스트 - 데이터가 문제없이 화면에 로드되는지 확인', async () => {
    const { findByTestId } = render(<Movie />);

    await findByTestId('loading-text');
    await findByTestId('contents-list');
    });

210504 Error) You should not use Link outside a <Router>


<StaticRouter>와 <MemoryRouter>를 사용

이번에 컴포넌트 단위로 단위테스트를 하던 중에 You should not use XXX and <withRouter> outside a <Router>에러를 접했다.
Router의 내부에서만 사용할 수 있는 <withRouter>와 <Link>를 사용한 컴포넌트를 테스트를 할때에는 개별 컴포넌트만 호출해서 렌더링한 후에 테스트를 진행하기 때문에 실제로는 Router 내에 구현이 되어있다고 하더라도 위와같은 에러가 발생하게 되는 것이다.

위의 에러에 대한 해결책은 테스트 코드에서 <MemoryRouter> 또는 <StaticRouter>를 사용해서 테스트하려는 컴포넌트를 감싸서 렌더링해주면 된다.
Header.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import Header from './Header';
import { MemoryRouter } from 'react-router';



describe('header 컴포넌트 내부의 메인 메뉴관련 테스트 코드', () => {
let header;
beforeEach(() => {
header = render(<MemoryRouter><Header/></MemoryRouter>)
});

describe('메뉴 클릭시 제대로 연결된 페이지가 렌더링되는지 확인하는 테스트 코드', () => {
test('Movie 메뉴 클릭시 Movie page component가 렌더링된다.', () => {
const { getByTestId } = header;
expect(getByTestId('movie-menu')).toBeTruthy();
fireEvent.click(getByTestId('movie-menu'));
expect(screen.findByTestId('movie-page-container')).toBeInTheDocument;
});
......

210219 [PyenvError] Pyenv BUILD FAILED ERROR


Pyenv로 새로운 Python version 설치시에 BUILD FAILED 에러 발생

이번에 프로젝트를 진행할때에는 Pyenv를 사용해서 프로젝트별로 파이썬 버전을 다르게 적용하고, Virtualenv로 별도의 가상환경을 구축하여 진행하였다. 문제가 발생한 시점은 Pyenv로 사용할 파이썬 버전을 설치하려고 하는데 아래와 같은 BUILD FAILED에러가 발생하였다.

문제(Issue)

Read more

210203 [SyntaxError] Non-ASCII Character

Resolve error

Python과 Pandas를 활용하여 csv파일을 읽는 간단한 처리를 하던 중에 Non-ASCII Character 에러가 발생했다.

1
SyntaxError: Non-ASCII character '\xec' in file /Users/hyungilee/Documents/dev/side-projects/corona-dashboard/pandas_practice.py on line 3, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Python의 경우, 기본적인 설정 상태에서 코드내에 한글이 있는 경우 코드 내의 한글을 Python 코드를 읽어들이지 못해서 위의 에러가 발생한다.

해결방법은 아래와 같이 파이썬 코드 맨 위 첫번째 혹은 두번째 줄에 한글 인코딩을 하도록 명령해주면 된다.

1
2
# -*- coding: utf-8 -*-
# -*- coding: euc-kr -*-
Read more