원자성.

여기서 말하는 원자성이란 더 이상 쪼갤 수 없다는 의미다.

원자성에 앞서 트랜잭션에 대해 간단히 정리하자.

What is transaction

  • 하나의 작업단위로 볼 수 있는 쿼리들의 집합이다.
  • 예를 들자면 송금을 들 수 있다.
    • Transaction BEGIN
    • 돈을 보내는 사람의 계좌의 잔액을 읽는다. (SELECT)
    • 돈을 보내는 사람의 계좌에서 돈을 출금한다. (UPDATE)
    • 돈을 받는 사람의 계좌에 돈을 입금한다. (UPDATE)
    • Transaction COMMIT OR ROLLBACK



트랜잭션 안에 있는 쿼리들은 모두 성공해야한다. 만약 실패하는 쿼리가 있다면 트랜잭션 내에 있는 모든 쿼리들의 결과는 롤백되어야 한다.

커밋이 되면 변경사항을 기록한다. 그런데.. 커밋이 된 게 아니라, 아직 트랜잭션이 진행 중이라면? 트랜잭션 안에 있는 단일 쿼리의 결과는 어디에 저장하지? 데이터베이스에 따라 다르다. 어떤 데이터베이스는 메모리에 올리고, 어떤 데이터베이스는 실제로 디스크에 쓰기도 한다. 트랜잭션 안에 있는 한 쿼리가 실패하거나, 아님 데이터베이스 시스템이 죽거나 등의 이유로 원자성에 의해 롤백이 되어야 하는데, 데이터베이스에 따라 롤백 작업이 굉장히 긴 시간을 소요하기도 한다.

만약 원자성이 없다면..?

Ex ) 송금 (ACCOUNT 의 ID 가 1 인 계좌에서 ID 가 2인 계좌로 100원을 송금한다.)
1. SELECT BALANCE FROM ACCOUNT WHERE ID = 1;
2. UPDATE ACCOUNT SET BALANCE - 100 WHERE ID = 1;
3. 데이터베이스 죽어버림

????? 내돈..

## 결론
Atomicity 의 의미 : 트랜잭션은 더이상 쪼갤 수 없는 하나의 작업이다!

'DB' 카테고리의 다른 글

트랜잭션 격리 수준(isolation level)  (0) 2020.07.14
[MySQL] InnoDB와 MyISAM 차이 (트랜잭션)  (0) 2020.07.07
UNION  (0) 2019.12.07
GROUP BY  (0) 2019.12.07

본 포스팅은 "알면 쉬운 도커 쿠버네티스" 책을 정리한 글입니다.
구매링크 : http://www.yes24.com/Product/Goods/91618364

해야할 일은 이렇다.

  1. nginx 이미지 다운로드.
  2. nginx index.html 변경
  3. nginx 이미지 기반에 2에서 변경한 index.html 커밋해서 새로운 이미지 만들기.
  4. 컨테이너화 해보기

nginx 이미지 다운로드

terminal에서 다음과 같은 명령어를 입력한다.
docker pull nginx

그 다음 docker images 명령어로 nginx 이미지가 제대로 다운로드 됐는 지 확인해보자.

nginx 이미지로 컨테이너 만들기

terminal 에서 다음과 같은 명령어를 입력한다.
docker run --name nginx -p 8080:80 -d nginx
이와 같은 명령어를 입력하면, 컨테이너 외부 8080 포트에 컨테이너 내부(nginx이미지 기반) 80포트와 매핑 시켜준다.
docker ps 명령어로 실행중인지 확인해보고, localhost:8080으로 접근해보자


nginx의 index.html 파일 변경.

현재 localhost:8080 으로 접속해서 보이는 페이지가 nginx 의 index.html 이다.
위치를 찾아보자.
먼저 컨테이너 내부로 들어가보자.
docker exec -it nginx /bin/bash
내부로 들어왔으면 다음과 같은 명령어로 index.html 을 찾아보자
find / -name index.html 2>/dev/null
이 명령어로 index.html의 위치를 찾을 수 있다 현재 위치는 /usr/share/nginx/html/index.html이다.

이 파일을 호스트로 복사하자. 먼저 exit명령어로 컨테이너 내부에서 빠져나온 뒤 다음과 같은 명령어를 실행하자.
docker cp nginx:/usr/share/nginx/html/index.html index.html
제대로 복사됐는지 ls -al 명령어로 확인해보자.

그 다음으로는 복사된 index.html 을 수정하자.

수정한 index.html 파일 nginx 이미지 내부에 집어넣기.

아까와 비슷하다 복사 명령어로 nginx 내부에 있는 index.html 파일을 우리가 변경한 index.html 파일로 덮어써버리자.
주의할 점은 이미지가 현재 컨테이너화 되어 있어야 한다. 실행중인 상태든, 정지된 상태든 괜찮다. 일단 컨테이너화 되어있어야 한다.
docker ps -a로 확인할 수 있다.

docker cp index.html nginx:/usr/share/nginx/html/index.html
현재 이름이 nginx 인 컨테이너의 위치에 index.html을 복사하는 명령어다.
localhost:8080으로 접근했을 때 띄워져있던 페이지를 새로고침해보자. 위에서 수정한 것처럼 titleHello GraceLove 로 바뀌었다.

수정한 컨테이너를 이미지화 시키기.

하지만 도커 이미지는 불변이다. 우리가 컨테이너를 지지고 볶고 하더라도, 이미지는 바뀌지 않는다.
실험해보고싶은 분들은 docker stop nginx , docker rm nginx 명령어로 컨테이너를 지우고 다시 docker run...을 해보자.

방법 1.

이제 우리가 변경한 컨테이너를 기반으로 도커 이미지를 만들어보자.
다음과 같은 명령어를 입력한다.
docker commit nginx mynginx
nginx라는 이름을 가진 컨테이너를 mynginx라는 이름을 가진 이미지로 만드는 것이다.
다음과 같은 명령어를 입력해서 image 가 됐는 지 살펴보자.
docker images | grep mynginx

방법2.

Dockerfile을 이용하자.
다음과 같이 만들자.
vi Dockerfile (vim의 사용법을 모르겠다면, 일반적인 텍스트 에디터를 사용해도 좋다.)
아래와 같은 내용을 채워넣는다.

FROM nginx
COPY index.html /usr/share/nginx/html/

이제 빌드를 해서 이미지를 만든다.
docker build -t mynginx2 .

다음과 같은 명령어를 입력해서 image 가 됐는 지 살펴보자.
docker images | grep mynginx2

우리가 만든 이미지와 기존 nginx 이미지를 컨테이너화 시켜서 비교해보기.

현재 실행 중인 모든 컨테이너를 지우자.
docker stop [container id] -> docker rm [container id]

이제 docker ps -a 을 하면 아무것도 나타나지 않아야 한다.

깨끗한 상태에서 우리가 만든 이미지와, 기존의 nginx 이미지를 컨테이너화 시켜서 비교해보자.
docker run --name mynginx -p 8080:80 -d mynginx2
docker run --name originnginx -p 9090:80 -d nginx

그 다음 각각 localhost:8080localhost:9090 으로 접속해보자.
우리가 만든 이미지는 컨테이너화 할 때 port 8080으로 연결시켰고, 기존의 nginx 이미지는 9090에 연결시켰다.
서로 다른 index 페이지를 보여주는 것을 볼 수 있다.

기억해야 할 만한기억해야 할 만한 글귀들을 적는 공간입니다. 개인적으로 이해하기 쉽게 바꾼 말도 있으니, 맥락만 봐주시길 바랍니다. 문서의 변경이 있을 수 있습니다.

모든 소프트웨어 모듈에는 세 가지 목적이 있다.

  1. 실행 중에 제대로 동작하는 것. 이것은 모듈의 존재 이유다.
  2. 변경을 위해 존재한다. 대부분 모듈은 생명주기 동안 변경되기 때문에 간단한 작업만으로도 변경이 가능해야 한다.
  3. 코드를 읽는 사람과 의사소통하는 것이다. 특별한 훈련 없이도 개발자가 쉽게 읽고 이해할 수 있어야 한다.

프로그래밍 패러다임의 공존. - 절차형 패러다임과 객체지향 패러다임은 공존할 수 없는가?

예를 들어 절차형 패러다임객체지향 패러다임 이 공존할 수는 없는 걸까?
서로 다른 패러다임이 하나의 언어 안에서 공존함으로써 서로의 장단점을 보완한다.
프로그래밍 패러다임은 과거의 패러다임을 폐기시키는 혁명적인 과정을 거치지 않는다.
오히려 과거 패러다임의 단점을 보완하는 발전적인 혁명을 거친다.

이는 비록 객체지향 패러다임을 주로 사용한다고 해도, 다른 패러다임을 배우는 것이 도움이 될 것이라는 사실을 암시한다.
은총알은 없다 라는 말을 기억하라. 객체지향이 적합하지 않은 상황에서는 언제라도 다른 패러다임을 적용할 수 있는 시야를 기르고, 지식을 갈고닦아야 한다.

객체 사이의 의존성 - 설계의 목표

의존성은 변경에 대한 영향을 암시한다. 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼있다.
그렇다면 객체 사이의 의존성을 완전히 없애야 하는 것인가? 아니다. 객체지향 설계는 서로 의존하며 협력하는 객체들의 공동체를 구축하는 것이다
따라서 설계의 목표는 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만드는 것이어야 한다.

설계는 균형의 예술이다.

  1. 어떤 기능을 설계하는 방법은 한 가지 이상일 수 있다.
  2. 동일한 기능을 한 가지 이상의 방법으로 설계할 수 있기 때문에 결국 설계는 트레이드오프의 산물이다.

설계는 균형의 예술이다. 훌륭한 설계는 적절한 트레이드오프의 결과물이다.

소프트웨어는 곧 생물이다. - 의인화

현실에서는 수동적인 존재라 하더라도, 객체지향의 세계에서는 모든 것이 능동적이고 자율적인 존재다.
현실의 전화는 서로에게 전화를 걸지 않으며, 색은 스스로 칠하지 않는다. 반드시 인간이 필요하다.
하지만 객체지향의 세계에서 객체들은 _능동적이고 자율적인 존재_다.
소프트웨어는 태어나고, 삶을 영위하고, 그리고 죽는다.

객체지향은 말 그대로 객체를 지향하는 것.

객체지향 패러다임으로의 전환은 클래스가 아닌 객체에 초점을 맞출 때만 얻을 수 있다. 다음 두 가지에 집중하자.

  1. 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.
    • 클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화한 것이다.
    • 따라서 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는 지를 먼저 결정해야한다.
    • 객체를 중심에 두는 접근 방법은 설계를 단순하고 깔끔하게 만든다.
  2. 객체는 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원이다.
    • 객체는 홀로 존재하지 않는다. 다른 객체에게 도움을 주거나 의존하면서 살아가는 협력적인 존재다.
    • 이 관점으로 바라보는 것은 설계를 유연하고 확장 가능하게 만든다.
    • 객체를 고립된 존재로 바라보지말고, 협력에 참여하는 협력자로 바라볼 것.
    • 객체들의 모양과 윤곽이 잡히면, 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고, 이 타입을 기반으로 클래스를 구현할 것.
    • 훌륭한 협력이 훌륭한 객체를 낳고 훌륭한 객체가 훌륭한 클래스를 낳는다.

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

7월 30일 객체지향 공부  (1) 2019.07.30

본 포스팅은 백기선님의 "더 자바, 애플리케이션을 테스트하는 다양한 방법" 을 보고 정리한 글 입니다.
관심 있으신 분들은 https://www.inflearn.com/course/the-java-application-test 를 살펴보세요

개요

테스트 자동화를 위한 Assertions 를 알아보자.

assertEquals

  • 이름에서 알 수 있다시피 기대하는 값과 실제 값이 동일한 지 검사하는 메서드다.

      static void assertEquals(Object expected, Object actual) {
          assertEquals(expected, actual, (String) null);
      }
    
      static void assertEquals(Object expected, Object actual, String message) {
          if (!objectsAreEqual(expected, actual)) {
              failNotEqual(expected, actual, message);
          }
      }
    
      static void assertEquals(Object expected, Object actual, Supplier<String> messageSupplier) {
          if (!objectsAreEqual(expected, actual)) {
              failNotEqual(expected, actual, messageSupplier);
          }
      }

사용법

    @Test
    void assert_test() {
        Study study = new Study(-10, 0);
        assertEquals(StudyStatus.DRAFT, study.getStatus(), "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.");
        assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.");
        assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
            @Override
            public String get() {
                return "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.";
            }
        });
    }
  • 첫 번째 인수
    • expected. 기대하는 값을 넣어준다.
  • 두 번째 인수
    • actual. 실제 값을 넣어준다.
  • 세 번째 인수
    • message. 실패했을 때 콘솔에 기록되는 message를 넣어준다.

세 번째 인수에 String 이나 Supplier을 넣어줄 수 있다.
차이점은 String을 넣는다면 테스트가 실패하든 성공하든 매번 메시지를 생성하지만, Supplier는 실패할 때만 메시지를 생성한다.
조그마한 성능차이에도 불안하다면 Supplier를 사용하자.


assertThrows

  • 예상하는 익셉션이 발생하는 지 확인한다.

      public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
          return AssertThrows.assertThrows(expectedType, executable);
      }

사용법

    @Test
    void assert_throws() {
        IllegalArgumentException illegalArgumentException =
                assertThrows(IllegalArgumentException.class, () -> new Study(10, -1));
        String message = illegalArgumentException.getMessage();
        assertEquals("최소 참석인원은 0 보다 커야 합니다.", message);
    }
  • 첫 번째 인수
    • Class expectedType. 발생하는 예외의 클래스를 넣어준다.
  • 두 번째 인수
    • Executable. 실제 행동을 넣어준다. 위 코드에서는 new Study(..) 를 했을 때 IllegalArgumentException이 발생하기를 기대한다.

assertAll

    public static void assertAll(Executable... executables) throws MultipleFailuresError {
        AssertAll.assertAll(executables);
    }
  • 원래 assertions 는 assertion 실패하면 그 밑의 코드는 더 이상 진행하지 않는다.
  • 만약 실패하더라도 다른 assertions 의 결과를 보고 싶다면 assertAll을 쓰자.

사용법

    @Test
    void assert_all() {
        Study study = new Study(-10, 0);
        assertAll(
                () -> assertNotNull(study),
                () -> assertEquals(StudyStatus.DRAFT, study.getStatus(),
                        () -> "스터디를 처음 만들면 " + StudyStatus.DRAFT + "상태 여야 합니다."),
                () -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 가능 인원은 0보다 커야 합니다.")
        );
    }
  • assertAll 안에 있는 모든 assertion을 확인한다.

assertTimeout

/**
     * <em>Assert</em> that execution of the supplied {@code executable}
     * completes before the given {@code timeout} is exceeded.
     *
     * <p>Note: the {@code executable} will be executed in the same thread as that
     * of the calling code. Consequently, execution of the {@code executable} will
     * not be preemptively aborted if the timeout is exceeded.
     *
     * @see #assertTimeout(Duration, Executable, String)
     * @see #assertTimeout(Duration, Executable, Supplier)
     * @see #assertTimeout(Duration, ThrowingSupplier)
     * @see #assertTimeout(Duration, ThrowingSupplier, String)
     * @see #assertTimeout(Duration, ThrowingSupplier, Supplier)
     * @see #assertTimeoutPreemptively(Duration, Executable)
     */
    public static void assertTimeout(Duration timeout, Executable executable) {
        AssertTimeout.assertTimeout(timeout, executable);
    }
  • 지정한 시간 내에 assertion 실행이 완료되지 않는다면 테스트가 실패한다.

사용법

    @Test
    void assert_timeout() {
        assertTimeout(Duration.ofMillis(100), () -> {
            new Study(10, 10);
            Thread.sleep(300);
        });
    }
  • 100 밀리세컨드 이내에 Executable 코드가 실행완료 되어야 한다.
  • 주의할 점은 100 밀리세컨드가 지나자마자 테스트가 실패하는 게 아니라, Executable 코드가 모두 완료된 후에야 테스트 성공/실패를 결정짓는다.

깃헙 링크

링크

+ Recent posts