반응형

* 본 아티클은, '좋은 코드 나쁜 코드' 4장을 기반으로 작성된 글입니다.

 

 

 


오류의 복구 가능성

  • 복구 가능한 오류 → 시스템 전체에 영향을 주지 않도록 적절히 처리
    • ex: 네트워크 오류 (몇 초 기다리고 재시고) / 통계 기록 오류 (무시)
  • 복구할 수 없는 오류 → 신속한 실패, 요란한 실패
  • 호출하는 쪽에서만 오류 복구 가능 여부를 알 때 → 오류를 복구하기 원할 것이라고 판단 (대부분의 경우)
    • 호출하는 쪽에서 오류를 인지하도록 해야 한다.

견고성 vs 실패

오류가 있더라도 계속 진행하는 견고성, 오류를 처리하게 하거나 프로그램 작동을 멈추는 실패 사이의 트레이드 오프 → 대부분의 경우 실패가 적절

신속하게 실패해야 하고, 요란하게 실패해야 한다.

오류 전달 방법

  • 명시적 방법 : 코드 계약 상 명확한 부분에 나타나 있어, 오류를 반드시 인지할 수 있음
    • ex : Checked exception, null 반환(null 안정성의 경우), Optional, Result, Outcome 반환(반환값 확인 필수인 경우)
  • 암시적 방법 : 오류를 인지하지 못 할 수 있음
    • ex : Unchecked exception, 매직값, Promise(Future), Assertion, Check, Panic

Result형의 경우, 올바른 사용 방법을 팀 내에서 공유하고 있다고 가정해야, 명시적 방법이 된다.

자바의 @CheckReturnValue 애너테이션을 사용하면 결과값(Outcome) 확인을 반드시 해야해서, 명시적 방법이 된다.

복구할 수 없는 오류의 전달

Unchecked exception을 발생하거나, panic이 되도록 하거나, check 또는 assertion을 사용한다.

Unchecked exception을 사용하는 이유는, 어차피 복구가 불가능한 상황에서 호출하는 모든 계층에 extends를 다는 것이 번거롭기 때문이다.

그저 오류를 호출한 쪽에 전달하면 되고, 스택 트레이스를 통해 위치를 파악할 수 있다.

호출하는 쪽에서 복구하기를 원할 수도 있는 오류의 전달

이 경우, Unchecked exception과 Checked exception(또는 명시적 기법) 사용을 두고 의견이 분분하다.

  • Unchecked exception을 사용해야 한다는 주장
    • 코드의 구조가 개선된다 (일관된 계층에서 오류 처리)
    • 실용적임
  • 명시적 기법을 사용해야 한다는 주장
    • 오류를 반드시 처리할 수 있고, 매끄럽게 처리할 수 있다.
      • 오류가 발생하는 지점에서 처리 or 던지기가 강제되므로, 발생 지점에서 오류를 인식하는 것만으로 매끄럽게 처리할 가능성이 높아진다.
    • 처리를 적절하게 하지 않아도, 코드 상에서 티가 잘 난다.
    • Unchecked exception이 적절하게 문서화되는 것은 어렵다.

필자는 명시적 방법을 추천한다.

하지만 많은 언어들이 Checked exception을 지원하지 않는 것에는, 실용적인 것을 포함하여 많은 이유가 있을 것이다.

아래 구절이 인상 깊다.

 

 

There is still one question left. What do water wings and checked exceptions have in common?

At the beginning you feel safer with them, but later they prevent you from swimming quickly.

Water wings가 안전하게 헤엄치는 것에 도움을 줄 수 있어도, 숙련되고 나면 빠르게 헤엄치는 것을 방해할 것이다.

컴파일러의 경고를 무시하지 마라

컴파일러의 경고를 잘 수정하는 것만으로도, 코드의 잠재적 문제를 해결할 수 있다.

반응형
반응형

 

* 본 아티클은, '좋은 코드 나쁜 코드' 3장을 기반으로 작성된 글입니다.

 

 

 


코드 품질의 핵심 요소 6가지

  • 가독성
  • 모듈화
  • 재사용성 및 일반화성
  • 테스트 용이성
  • 예측 가능한 코드를 작성하라
  • 코드를 오용하기 어렵게 만들라
    본 장은 이 중 ‘예측 가능한 코드를 작성하라’와 ‘코드를 오용하기 어렵게 만들라’는 2개와 깊게 관련이 있다.

코드를 작성할 때 다음의 3가지를 반드시 기억해야 한다.

  • 자신에게 분명하다고 해서 다른 사람에게도 분명한 것은 아니다.
  • 다른 개발자는 무의식 중에 당신의 코드를 망가뜨릴 수 있다.
  • 시간이 지나면 자신의 코드를 기억하지 못한다.

따라서, 코드를 작성할 당시에 다른 사람(미래의 자신 포함)에게도 분명하며, 망가뜨리기 어려운 코드를 작성해야 한다.

내가 작성한 코드를 다른 사람들이 파악하는 방법

  • 이름 확인
  • 데이터 타입 확인
  • 문서 읽기

여기까지가 실용적인 방법이고,

  • 직접 묻기
  • 자세한 구현 코드 읽기

위 두 가지는 비현실적이고 비효율적인 방법이다.

코드 계약

서 다른 코드 간의 상호작용을 계약처럼 생각한다.

계약에서 정의한 대로 분명하게 실행되고, 예상과 다르게 실행되는 것이 없어야 한다.

  • 선결 조건 : 코드를 호출하기 전에 사실이어야 하는 것
    • ex: 실행 전 시스템의 상태, 코드에 공급해야 하는 입력
  • 사후 조건 : 코드가 호출된 후에 사실이어야 하는 것
    • ex: 시스템이 새로운 상태에 놓였는지, 반환값
  • 불변 사항 : 코드 호출 전후가 동일해야 하는 사항

명백한 사항 / 세부 조항

전기 스쿠터 렌탈 앱을 예로 들자.

  • 핵심 사항
    • 전기 스쿠터 대려
    • 시간 당 10달러
  • 세부 조항
    • 충돌 사고 시 수리 비용 지불
    • 시속 30마일 이상 → 벌금 부과

이중, 시속 30마일 이상에 벌금을 부과하는 항목은 당황스러운 조항일 수 있다.

코드에서는,

  • 핵심 사항
    • 함수와 클래스 이름
    • 인자 타입
    • 반환 타입
    • Checked exception
  • 세부 조항
    • 주석, 문서
    • Unchecked exception

핵심 사항으로 조건을 명백하게 하는 것이 좋다.

문서, 주석은 업데이트가 어려워 믿기 힘들다.

Unchecked exception은 처리가 강제되지 않아서 놓칠 수 있다.

세부 조항에 대한 의존성을 최대한 줄이고,

핵심 사항만으로 명확하게 코드를 설명할 수 있도록 하자.

코드 예시

class UserSettings {
    UserSettings() { ... }

    // 이 함수를 사용해 설정이 로드되기 전에 다른 함수 호출 X
    Boolean loadSettings(File location) { ... }

    // 이 함수는 loadSettings() 함수 호출 이후, 다른 함수 호출 이전에 호출해야 함
    void init() { ... }

    // 사용자가 선택한 UI의 색상 반환
    // 선택된 색상이 없으면 null 반환
    // 설정이 로드 또는 초기화되지 않았으면 null 반환
    Color? getUiColor() { ... }
}

클래스, 메서드명을 통해 역할을 충분히 파악할 수 있지만,

loadSettings() → init() → getUiColor() 순서로 실행되지 않으면 문제가 발생한다.

void setUiColor(UserSettings userSettings) {
    Color? chosenColor = userSettings.getUiColor();
    if (chosenColor == null) {
        ui.setColor(DEFAULT_UI_COLOR);
        return;
    }
    ui.setColor(chosenColor);
}

위의 setUiColor에서 chosenColor의 null 가능성을 체크하지만, 이것이 상태의 오류인지, 사용자가 선택한 색상이 없음을 나타내는 것인지 알 수 없다.

상태의 오류가 발생해도 → 프로그램은 계속 동작한다.

세부 조항을 제거하는 방법

  • 정적 팩토리 메서드 → 설정 로딩, 초기화가 완료된 인스턴스만 반환하도록 함
    • getUiColor()가 null을 반환하면 → 색상을 선택하지 않았다는 뜻임
  • 생성자를 private으로 설정
  • 설정 관련 메서드 private 설정
class UserSettings {
    private UserSettings() { ... }

    static UserSettings? create(File location) {
        UserSettings settings = new UserSettings();
        if (!settings.loaadSettings(locations)) {
            return null;
        }
        settings.init();
        return settings;
    }

    private Boolean loadSettings(File location) { ... }

    private void init() { ... }

    Color? getUiColor() { ... }
}    

상태, 가변성이 클래스 외부로 노출되는 것을 없앴다. → 버그 침투 가능성을 낮췄다.

체크, 어서션 (Check, Assertion)

  • 체크 (Check)
    • 전제 조건 검사 : 입력 인수가 올바르거나, 초기화가 수행되었거나, 일부 코드를 실행하기 전에 시스템의 유효성을 확인
    • 사후 상태 검사 : 반환값 또는 코드 실행 후 유효성 확인
  • 어서션 (Assertion)
    • 체크와 매우 비슷하지만, 배포를 위해 빌드할 때 보통 컴파일에서 제외된다.
      • 때문에, 실제 서비스 환경에서의 실패를 명백하게 보여주지는 않는다
Color? getUiColor() {
    assert(hasBeenInitialized(), "UserSettings가 초기화되지 않음");
    ...
}

위의 코드는 어서션의 예시이다. (테스트 코드에서 많이 보았을 것이다.)

두 가지 모두 개발 또는 테스트 단계에서 코드의 오용을 파악하는 것을 목적으로 하지만, 효과가 보장되지는 않는다.

코드 계약에 세부 조항이 있을 때 체크나 어서션을 사용하면 좋다.

하지만 가능하다면 애초에 세부조항을 피하는 것이 좋다.

반응형
반응형

* 본 아티클은, '좋은 코드 나쁜 코드' 2장을 기반으로 작성된 글입니다.

 

 

 


추상화 계층을 만들면 복잡한 문제를 간단하게 해결할 수 있다.

다음은 HTTP 통신을 통해 서버에 메시지를 보내는 코드의 예시이다.

HttpConnection connection = HttpConnection.connect("http://example.com/server");
connection.send("Hello server");
connection.close();

높은 단계에서 바라보면 이것은 꽤 간단해 보이는데,

사실 이 과정에서는 문자열 직렬화, HTTP 프로토콜의 복잡한 동작, TCP 연결, 네트워크 연결 확인, 신호 변조, 데이터 전송 오류 수정 등의 복잡한 과정들이 일어난다.

하지만 HttpConnection 라이브러리(모듈)를 사용하는 입장에서는 추상화된 개념만 이해하면 될 뿐이다.

이를 통해 코드 품질의 4가지 핵심 요소를 달성할 수 있다.

  • 가독성 : 추상화된 개념만 이해하면 된다.
  • 모듈화 : 추상화된 모듈을 다르게 구현된 모듈로 쉽게 변경할 수 있다.
  • 재사용성 및 일반화성 : 간결하게 추상화된 계층은 재사용하기 쉽고, 다른 상황에서 일반화되기 쉽다.
  • 테스트 용이성 : 간결하게 나눠진 부분에 대해서만 완벽하게 테스트하면 된다.

추상화 계층은 다음의 단위로 코드를 분할하고 의존하게 함으로써 나눌 수 있다.

  • 함수(메서드)
  • 클래스(및 구조체나 믹스인과 같이 클래스와 비슷한 요소도 가능)
  • 인터페이스
  • 패키지, 네임스페이스, 모듈

API, 함수

API는 인자, 반환 타입, 메서드명에 대해서만 공개하고, 세부 구현은 숨긴다.

API로 노출함으로써 추상화 계층을 유지할 수 있다.

함수가 너무 많은 일을 한다면, 작은 함수로 나눌 수 있다.

클래스

클래스의 추상화 기준에 대한 다양한 이론과 경험 법칙이 있다.

  • 줄 수 : 코드 300줄 이하 같은 기준은 대체로 맞지만, 상황에 따라 다를 수 있어 정확하지 않다.
  • 응집력
    • 순차적 응집력 : 한 요소의 출력이 다른 요소에 대한 입력으로 필요하다면 순차적인 응집력이 있다고 볼 수 있다.
      • ex: 커피 만들기(원두를 갈아서 → 추출)
    • 기능적 응집력 : 하나의 일을 성취하는 데 기여하느닞
  • 관심사의 분리 : 시스템이 각각 별개의 문제를 다루는 개별 구성 요소로 분리되어야 한다
    • 게임 콘솔은 TV와 독립적이다.

응집력을 고려하고 관심사의 분리가 적절하게 이루어졌다면,

코드 가독성이 좋아지고,

코드가 모듈화 되고 상호작용을 몇 가지 퍼블릭 함수를 통해서만 이루어져 구현 클래스 교체가 쉬워지고,

재사용과 일반화과 쉬워지고,

테스트가 쉬워진다.

텍스트 요약 클래스 예시

TextSummarizer 라는 클래스는,

  • 텍스트를 단락으로 분할
  • 텍스트 문자열의 중요도 점수 계산
  • 중요도에 따라 텍스트 요약

위와 같은 책임을 가진다.

이는 코드 품질의 4가지 핵심 요소에 반한다.

단락을 분할하는 ParagraphFinder와, 문자열의 중요도를 계산하는 TextImportanceScorer를 분리하고,

TextSummarizer에 의존성을 주입시킨다.

class TextSummarizer {
    private final ParagraphFinder paragraphFinder;
    private final TextImportanceScorer importanceScorer;

...
}

이를 통해 가독성이 좋고, 모듈화 되고, 재사용 일반화가 쉽고, 테스트가 쉬운 코드가 될 수 있다.

인터페이스

위의 예시에서 TextImportanceScorer의 로직을 바꾸어, 기계학습 모델링을 적용하고 싶다고 하자.

이럴 때는, TextImportanceScorer 클래스를 인터페이스로 추출한 다음,

이 문제(문자열 중요도 점수 계산)를 해결하는 각각의 방식을 서로 다른 클래스로 구현하며 된다.

WordBasedScorer / ModelBasedScorer

코드를 작성하다 보면, 주어진 추상화 계층에 한 가지 구현만 있고, 향후에 다른 구현을 추가할 계획이 없는 경우가 있다.

그럼에도 인터페이스를 통해 추상화 계층을 표현하면 몇몇 이점이 있다. (이 경우 상위 계층은 인터페이스에 의존할 뿐, 구현 클래스에 직접 의존하지 않는다.)

  • 퍼블릭 API를 명확하게 알 수 있다 (인터페이스를 보고)
  • 나중에 구현이 추가될 가능성이 있다
  • 테스트가 쉽다 (목, 페이크로 대체 가능)
  • 같은 클래스로 두 가지 하위 문제를 해결할 수 있다.

물론 작업이 더 필요하고, 코드가 복잡해질 수 있다는 단점도 있다.

층이 너무 얇아질 때

무리하게 계층을 나누면, 계층을 나누기 위해서만 계층을 나누는 무의미한 일이 될 수 있다.

하지만, 나눌지 말지 헷갈리는 경우에는 대부분 나누는 것이 더 좋다.

안 나눴을 때의 단점이 더 크기 때문.

마이크로서비스에 대해서는 어떤가?

개별 문제에 대한 해결책을, 독립적으로 실행되는 서비스로 배포하는 구조를 MSA(MicroService Architecture)라고 한다.

예를 들어, 온라인 소매업체에서 재고를 확인하고 수정하는 마이크로서비스를 개발할 수 있겠다.

이 경우, MSA 자체가 시스템을 분리하고 모듈화하지만,

서비스 구현을 위해 해결하는 하위 문제들을 추상화할 필요성은 변하지 않는다!

반응형
반응형

 

* 본 아티클은, '좋은 코드 나쁜 코드' 1장을 기반으로 작성된 글입니다.

 

 

 


코드 품질의 목표

  • 작동해야 한다.
  • 작동이 멈추면 안 된다.
  • 변경된 요구 사항에 적응할 수 있어야 한다.
    • 수정, 기능 추가, 소비자 선호나 비즈니스 요구 변경 등
  • 이미 존재하는 기능을 다시 구현해서는 안 된다.

코드 품질의 핵심 요소

  1. 코드는 읽기 쉬워야 한다.
    • 코드의 가독성이 떨어지면, 다른 개발자가 그 코드를 이해하는 데 많은 시간을 들여야 한다.
    • 코드의 기능이나 세부 사항을 놓칠 가능성도 크다. → 버그 확률 up
  2. 코드는 예측 가능해야 한다.
    • 사람의 정신 모델에 반하도록 구현하면 안 된다.
    • 중요한 데이터가 손상되는 재앙 등이 발생할 수도 있다.
  3. 코드를 오용하기 어렵게 만들라.
    • TV 뒷면의 전원 소켓과 HDMI 소켓처럼
  4. 코드를 모듈화하라.
    • 잘 정의된 인터페이스를 사용하여 두 개의 인접한 모듈 사이 상호작용을 만든다.
  5. 코드를 재사용 가능하고 일반화할 수 있게 작성하라.
    • 재사용성 : 드릴로 벽과 바닥에 모두 구멍을 뚫을 수 있음
    • 일반화성 : 드릴의 회전으로 나사도 박을 수 있음
  6. 테스트가 용이한 코드를 만들고, 제대로 테스트하라.
    • 테스트는 필수적이다.
    • 테스트 용이성 : 테스트 대상이 되는 ‘실제’코드가 얼마나 테스트하기 적합한지 나타내는 개념으로, 코드를 작성하면서 테스트 용이성을 반드시 고려해야 한다.

고품질 코드를 작성하는 것은 오래 걸리지만, 중장기적으로는 개발 시간이 단축된다.

 

 

 

 

반응형
반응형

1장 - 효율적으로 언어 배우기

프로그래밍 언어에 의존하지 않는 지식이 필요하다

본 책에서는 서로 다른 프로그래밍 언어 간의 비교, 프로그래밍 언어 탄생과 발전의 역사를 통해 학습한다.

 

 

2~4장

프로그래밍 언어와 규칙은 필요에 의해 탄생했다.

반복적인 작업을 줄이거나, 가독성을 개선하는 등의 이유로.

언어마다 탄생하게 된 배경, 해결하고자 한 문제가 다르기에, 가지고 있는 특징과 강점 모두 다르다.

언어를 사용하는 사용자는 그러한 배경과 특징을 알고서 자신에게 유리한 언어를 고민하고 사용해야 한다.

 

연산 순서는 기본적으로 모두 구문 트리(이진 트리 형태)를 따른다. 그것을 표기하는 방식만 다를 뿐이다.

 

if, else, for, while 등은 모두 C의 goto((어셈블리어에서는 jump))로 처리 가능하다.

하지만 가독성, 반복을 줄이기 위해서 등의 이유로 탄생하고 사용되는 것이다.

 

 

5장 함수

함수는 반복되는 큰 조직을 작은 부서로 나누는 것과 비슷하다.

또한 반복되는 작업을 줄이기 위함도 있는데, 반복되는 코드를 재실행하는 비용은 그리 크지 않다.

 

함수를 호출하려면 다시 돌아갈 위치를 기록할 메모리가 필요하다.

호출한 함수가 또 다른 함수를 호출한다면, 메모리에 하나의 주소만 저장해서는 프로그램을 제대로 실행시킬 수 없다.

그래서 호출한 함수들을 실행하고 돌아갈 주소 값을 스택에 저장한다.

이를 응용하여 재귀 호출도 구현할 수 있다.

 

깊이가 n으로 깊어지는 반복문은, n이 정해져 있지 않다면 구현할 수 없다.

하지만 재귀 호출을 통해 해결할 수 있다.

HTML처럼 수많은 내포 구조의 데이터를 다루기엔 재귀 호출이 적절하다

 

 

6장 예외 처리

코드 실행이 실패하는 경우가 있다.

이 실패를 처리하는 방식은 두 가지로 나뉜다.

  1. 반환값으로 확인 후 처리한다.
  2. 실패할 경우 점프한다.

C언어를 비롯한 많은 언어들은 1번을, Java를 비롯한 많은 언어들은 2번을 사용한다.

 

예외를 처리할 때 가장 중요한 것은 놓치지 않는 것이다.

놓치지 않은 예외는 프로그램 종료로 이어질 수 있으므로.

그를 위한 효율적 수단인 Java의 Checked exception은 thorws를 통해 반드시 명시적으로 발생 가능성을 표현하거나 try-catch를 통해 예외를 처리해야 한다. 하지만 하나의 메서드가 thorws할 경우 상위의 모든 메서드가 throws를 해야할 수 있다. 이는 매우 불편하므로 잘 사용되지 않는다.

 

예외를 처리하는 방식을 선택할 때는 놓치지 않는 것과 코드의 복잡성 두 가지를 모두 고려해야 한다.

 

 

7장 이름과 스코프

변수와 함수에 이름을 붙이는 것은, 번호로 무언가를 찾는 것보다 인간에게 직관적이기 때문이다.

 

그렇게 붙인 이름이 중복되는 경우가 발생한다.

초기에는 하나의 대응표를 사용했는데, 여러가지 문제가 발생했다.

 

해결하기 위해 나온 방법인 동적 스코프는, 함수 내부에서 전역 변수의 값을 바꿔서 사용하고, 종료 전에 다시 바꿔놓는 방법이다.

하지만 이 방법은, 해당 함수 내부에서 새로 호출한 함수가 전역 변수를 사용할 경우, 바뀐 값을 참조한다는 문제가 발생한다.

 

이를 다시 해결한 방법이 정적 스코프이다. 함수마다, 정확히는 함수 호출마다 새로운 대응표를 만들고, 함수가 종료되면 대응표를 삭제하는 방식이다.

 

이 또한 완벽한 방법은 아니지만, 대부분의 상황에서 잘 작동하므로 널리 사용되고 있다.

 

 

8장 형

컴퓨터는 숫자를 어떻게 표현할까?

0~9에 대응되는 비트를 할당하고, 각각을 on/off로 표현함으로써 숫자를 표현할 수 있을 것이다.

이는 지극히 비효율적이므로, 자연스러운 과정을 거쳐 이진법을 사용하는 것으로 변모했다.

 

그러다 소수를 표현할 일이 생겼고, 소수점 자리수를 고정하는 고정 소수점을 사용했었으나,

유연함을 위해 부동 소수점을 사용할 일이 생겼다. 소수점이 어디 위치하는지 자료 자체가 값을 가지고 있는 형태를 의미한다.

 

그리고 정수와 부동 소수점 자료를 구분하기 위해 형이 탄생했다.

 

발전된 형태의 형은, 여러 형을 묶어서 새롭게 만들 수도 있게 되었고,

형 자체를 인자로 받아, 내부의 형을 결정할 수도 있게 되었다(C++의 Templete, Java의 Generics 등)

 

 

9장 컨테이너와 문자열

배열과 연결 리스트처럼 값을 넣기 위한 상자가 있다.

배열은 인덱스를 통해 값을 조회하기에 O(1)의 시간 복잡도로 값을 조회할 수 있다.

하지만 값을 추가하려면, O(n)의 계산량을 가진다. 특정 인덱스 이후의 값을 모두 복사해야하기 때문.

연결 리스트는 반대로, 값 추가는 O(1)의 시간 복잡도를 가지지만, 값 조회는 O(n)의 시간 복잡도를 가진다.

 

문자열을 통해 값을 찾는 사전은, 해쉬 테이블과 트리로 대개 구현한다.

해쉬 테이블은 값 조회 시 해쉬 함수 1회만 호출하면 주소값을 알 수 있기에, O(1)의 시간 복잡도를 가진다. 하지만 메모리 소비량이 매우 크다.

트리는 값을 꺼내는 처리가 O(log n)의 시간 복잡도를 가진다.

증가할 가능성이 있는 것과, 그것의 시간 복잡도를 고려해서 자료 구조를 선택해야 한다.

 

모스 부호로부터 발전되어온 문자 체계는, 아스키를 거쳐 유니코드에까지 이르렀다.

 

 

10장 병행 처리

병행 처리는 병렬(하드웨어)과 다르게 소프트웨어적 개념이다.

처리를 변경하는 2가지 방법이 있는데,

하나는 협력적 멀티태스크로, 처리가 일단락되는 시점에 자발적으로 처리 교대를 하는 방법이다.

둘째는 선점적 멀티태스크로, 태스크 스케줄러가 처리를 강제로 중단시킨다. (시간 등의 이유로)

하지만 스레드 세이프하지 않은 상황이 발생할 수 있다.

 

스레드 세이프하지 않은 상황은

  1. 2가지 처리가 변수를 공유
  2. 적어도 하나의 처리가 그 변수를 변경
  3. 한쪽 처리가 마무리 되기 전에 다른 한쪽의 처리가 끼어들 가능성이 있음

위 세 가지 조건을 모두 만족해야만 한다.

반대로, 어느 한 조건만 막을 수 있어도 스레드 세이프하다.

 

공유하지 않는 접근법은 너무 엄격해서 구현하기 어려웠다.

변경하지 않는 방법도 존재한다.

끼어들지 않는 방법은, 협력적 스레드를 만드는 것이다.

또는 끼어들면 곤란해지는 처리에 표식을 붙인다. (락, 뮤텍스, 세마포어)

하지만 데드락이 발생할 수 있다.

데이터베이스의 트랜잭션 기법을 메모리에 적용해, 별도의 버전에 변경을 적용하다가, 마지막 단계에서 실제 메모리에 변경을 적용하는 방법이 있다. 하지만 잦은 호출이 일어나면 성능이 나빠질 수 있다.

트랜잭션 메모리 구현은 하드웨어, 소프트웨어를 거쳐 다시 하드웨어로 구현하는 것이 대세가 되었다.

 

 

11장 객체와 클래스

객체지향의 탄생에 앞서, 프로그래머에겐 변수와 함수를 합쳐서 모형을 만들고 싶다는 목적이 있었다.

모듈과 패키지로 인수, 초기화 처리 등의 해쉬를 묶는 방법이 있다.

Javascript에서처럼 함수도 해쉬에 넣는 방법이 있다. 함수를 퍼스트 클래스(변수에 대인, 인수로 전달, 반환값으로 사용 등이 가능한 값)로 사용한다는 것이다.

이를 통해 복수 개의 동일하지만 주소가 다른 객체를 생성할 수 있었고, 공유하는 내용을 프로토타입으로 이동시킬 수도 있었다.

그리고 클래스는 새로운 형을 만들고, 조작에 대한 사양을 정의하려고 탄생했다.

  1. 결합체를 만드는 생성기
  2. 어떤 조작이 가능한지에 대한 사양
  3. 코드를 재사용하는 단위

1번은 앞선 사례에서도 사용된 예시이고, 2번은 Java에서 인터페이스로 만들어졌다.

3번은 상속을 통한 것인데, 다음 장에서 다룬다.

 

 

12장 상속을 통한 재사용

상속은 3가지 측면으로 접근할 수 있다.

1. 일반화 / 특수화

흔히 말하는 is-a 관계에 해당한다.

 

2. 공통 부분을 추출

이는 is-a 관계에 해당하지 않는다.

 

3. 차분 구현

대부분 is-a 관계가 아니며, 상속을 재사용을 위해 사용하고, 변경된 부분만 추가로 구현한다.

 

리스코프 치환원칙을 지켜야 하지만, 속성이 늘어날 때 상속 관계로부터 리스코프 치환원칙이 지켜지지 않을 수 있다.

 

다중 상속은 편리하지만 다루기 어려운 도구이다.

코드 재사용에 매우 편리하지만, 메서드나 변수가 충돌하는 문제가 생길 수 있다.

해결책을 짚어보자.

 

1. 다중 상속을 금지한다. → Java

위임 (조합) → 이는 객체를 상속하는 게 아니라 보유함으로써 중복 코드를 제거한다. 이를 하드코딩하는 것이 아니라, 설정 파일을 통해 실행 시에 주입하는 것이 편리하다는 발상에서 ‘의존성 주입’이 탄생했다.

Java는 다중 상속을 금지하지만, Interface에 한해서는 가능하다.

 

2. 메소드 해결 순서를 고민한다.

상속받은 클래스의 메소드 중 어느 것을 사용할 지 해결하는 알고리즘을 사용하는 것이다. DFS, C3 선형화 등의 방법이 사용된다.

 

3. 처리를 섞는다.

Python의 Mixin처럼 재사용하고 싶은 기능만을 모은 작은 클래스를 만들어, 해당 기능을 추가하고 싶은 클래스에 섞어 넣는다.

 

4. 트레이트(Trait)

클래스는 2가지 상반되는 역할이 있다.

첫째는, 인스턴스를 만들기 위한 것.

둘째는, 재사용 단위.

Java에서는 클래스를 첫째의 역할로 주로 사용하고, 둘째의 역할로는 사용하는 것을 권장하지 않는다.

이 중 둘째의 역할에 특화된 작은 구조(메소드 묶음)가 트레이트이다.

 

어느 것이 정답인지는 모른다. 그리고 각각의 상황에 따라 정답이 달라질 수도 있다.

트레이트는 다양한 언어에서 도입되고 있다. 이후에 가장 유망한 기술로 자리잡을 수도 있다.

하지만 10년, 20년 후 보다 좋은 해결책이 고안되어, 동적 스코프 같이 잊혀질 수도 있다.

 

단, 클래스가 ‘재사용 단위’, ‘인스턴스 생성기’로서의 역할을 모두 가지고 있고, 그것은 상반된 것이라는 ‘트레이트’의 생각 방식은 매우 훌륭해 보인다고 저자는 말한다.

과연 트레이트는 정적 스코프나 while문 같이 당연한 존재로 자리잡을 것인가?

 

 

 

 

 

 

반응형
반응형

 

public class Practice implements A, B {
	...
}

interface A {
    default void print() {
        System.out.println("hi");
    }
}

interface B {
    default void print() {
        System.out.println("hello");
    }
}

위처럼 Practice가 구현하는 A, B 인터페이스가 동일한 시그니쳐의 default 메서드를 가지면 어떻게 될까? 라는 궁금증이 생겼습니다.

 

확인해보니 아래와 같은 에러가 발생합니다.

A와 B로부터 연결되지 않은 default 메서드들을 상속받았다는 것입니다.

 

public class Practice implements A, B {

    @Override
    public void print() {
        A.super.print();
    }
}

위와 같이 Overrinding을 통해 해결해야 합니다.

 

public class Practice implements A, B {

    @Override
    public void print() {
        A.super.print();
    }
}

interface A {
    default void print() {
        System.out.println("hi");
    }
}

interface B {
    default int print() {	// 반환 타입만 다름
        System.out.println("hello");
    }
}

 위처럼 인자는 같은데 반환타입만 다른 경우는, 충돌이 해결되지 않습니다.

 

public class Practice implements A, B {
    
}

interface A {
    default void print() {
        System.out.println("hi");
    }
}

interface B {
    default void print(int a) {
        System.out.println(a);
    }
}

위처럼 (반환타입과 관계 없이) 인자가 다른 경우는, Overring을 하지 않아도 됩니다.

클래스 생성 시에 Overloading이 되는 것으로 생각됩니다.

 


Java는 클래스의 다중 상속을 지원하지 않는데, Interface의 다중 상속(구현)은 가능합니다.

이는 Interface가 메서드의 구현이 없는 껍데기만 존재하기 때문에, 다중 상속에서 발생할 수 있는 충돌 문제로부터 안전하다고 판단했기 때문인데요,

 

편리함을 위해 추가한 Interface의 Default 메서드가 충돌의 원인이 될 수 있다는 것을 알았습니다.

편리함에는 항상 대가가 따르는 것 같습니다.

 

 

 

 

 

 

 

반응형
반응형

Null을 왜 사용하지 말아야 할까?

Null의 개념을 최초로 도입한 토니 호어(Tony Hoare)

null 참조를 만든 것은 Billion-dallor mistake(1조 짜리 실수)라고 했습니다.

Null을 사용함으로 인해, 무수한 에러들이 발생했기 때문입니다.

출처 : Sapienz: Multi-objective Automated Testing for Android Applications(University College London)

위 그래프는 안드로이드 앱의 결함 원인 통계입니다. NullPointer 에러로 인한 결함이 2위이고, Native crash 원인 내부에도 NullPointer로 인한 결함이 있을 수 있다고 합니다.

 

Null을 반환하는 API가 있다고 하면, 그것을 사용하는 사람이 꼼꼼하게 Null 가능성에 대해 점검을 해야 합니다. 하지만 모든 객체에 대해 Null 가능성을 점검하는 일은 쉽지 않습니다.

 

두 가지 방법으로 이를 해결할 수 있는데요, (더 많은 방법이 존재하긴 합니다.)

  1. Null을 안전하게 다루기
  2. Optional을 사용하기

하나씩 살펴보겠습니다.

 

 

Null을 안전하게 다루기

Null을 반환하지 말자.

  • null 반환 대신 예외 발생
  • Collection의 경우 emptyList, emptySet 등 사용
  • Null 객체를 구현 (Null object pattern 사용 / 참고 링크(클릭))
  • Optional 반환 → 아래에서 알아볼 예정

 

Null의 범위를 지역에 제한하라.

  • 부득이하게 null을 사용해야 한다면, 지역 변수(메서드, 생성자 내부)로만 사용한다.
    • 불가피하다면 클래스 영역에서만 사용한다.

 

초기화를 명확히 하라.

  • 실행 시점에 초기화되지 않은 필드가 없게 하라.
  • 실행 시점에 null인 필드는, 초기화되지 않았다는 의미가 아니라, 값이 없다는 의미여야 한다.

그 외에도 많은 내용들이 있지만, 개인적으로 와닿고 이해되는 부분을 정리해보았습니다.

 

 

Optional 올바르게 사용하기

Optionalnull일 수도 있는 값을 wrapping한 클래스입니다.

 

Optional 사용의 기본 원칙

1. Optional은 클래스의 필드로 사용하면 안 됩니다.

정확히는 사용할 수는 있으나, 설계 목적에 반하는 사용입니다.

Optional은 반환 값이 null일 수도 있음을 알려주기 위해 만들어졌습니다.

그래서 데이터를 직렬화(Serialize)할 수가 없습니다.

 

2. Optional은 메서드, 생성자의 인자로 사용하면 안 됩니다.

메서드나 생성자에서는 Optional을 받든 아니든 null 체크를 반드시 하는 것이 안전합니다.

굳이 비용이 비싼 Optional을 사용할 필요가 없습니다.

정리하면, Optional은 반환 타입으로만 사용하는 것이 옳습니다.

 

Optional 메서드

Optional에는 다양한 메서드가 존재합니다.

그에 대해 간단히 용법을 설명해보겠습니다.

  • empty() : null을 담고 있는 Optional 인스턴스를 생성합니다. (Optional 내부적으로 미리 생성해놓은 싱글톤 인스턴스입니다.)
  • of(T value) : Optional 인스턴스를 생성해, 인자로 받은 값을 저장합니다. null이 입력되면 NPE(NullPointerException)이 발생합니다.
  • ofNullabe(T value) : of와 동일하나, null이 입력될 수 있습니다.
  • get() : 값이 있다면 반환하고, 없다면 NoSuchElementException이 발생합니다. 그냥 안 쓰는 것이 좋습니다.
  • orElse(T value) : 값이 있다면 반환하고, 없다면 orElse() 메서드 내부의 값을 반환합니다.
  • orElseGet(Supplier<? extends T> other) : 값이 있다면 반환하고, 없다면 인자로 받은 Supplier를 실행합니다. 함수를 실행한다는 뜻입니다. (orElselazy한 버전이라고 생각해주시면 됩니다.)
  • orElseThrow(Supplier<? extends X> exceptionSupplier) : 값이 있다면 반환하고, 없다면 Supplier로 넘어온 예외를 던집니다.
  • isPresent() : 값이 있다면 true, 없다면 false를 반환합니다.
  • isEmpty() : isPresent()와 반대입니다. 값이 없을 때 true를 반환, 있다면 false를 반환합니다.
  • ifPresent(Consumer<? super T> consumer) : 값이 존재하는 경우 consumer 로직을 실행합니다.
  •  

Tip1. orElse(new Object()) 보다, orElseGet(() → new Object())를 사용한다.

orElse(new Object()) 를 사용하는 경우, orElse에 도달하기 전에 new Object() 명령이 실행됩니다.

하지만, orElseGet(() → new Object())의 경우 orElse에 도달하는 경우 즉, Optional 내부에 값이 없는 경우에만 Supplier가 실행되므로 객체 생성 비용을 아낄 수 있습니다.

 

Tip2. 단지 값을 얻을 목적이라면 Optional 대신 null을 비교한다.

// X
return Optional.ofNullable(status).orElse(READY);

// O
return status != null ? status : READY;

 

Tip3. primative 자료형의 경우 OptionalInt, OptionalLong, OptionalDouble을 사용한다.

자료형을 다시 Boxing, Unboxing하지 말고, 이미 준비된 클래스를 사용하는 것이 좋습니다.

 

 

Optional의 활용

Optional은 0~1개의 원소를 가지고 있는 Stream으로 생각할 수 있습니다.

직접 구현, 상속 관계가 있는 것은 아니지만, 사용 방법이나 사상이 유사하기 때문입니다. (함수형 인터페이스 구조)

따라서 map(), flatmap(), filter() 등의 메서드를 사용할 수 있습니다.

public Optional<Name> getNameIfLengthOverStandard(Player player, int standard) {
    return Optional.ofNullable(player)
            .filter(player -> player.getName().length() > standard)
            .map(Order::getName);
}

위처럼, 특정 조건을 만족하는 경우에는 값을, 아닌 경우 null을 담고 있는 Optional을 반환할 수도 있습니다.

과정에서 map()을 통해 Player의 필드인 Name으로 자료형을 치환했습니다.

 

 


Null의 위험성과 올바르게 사용하는 방법,

그리고 Optional을 통해 Null-safe한 코드를 작성하는 방법을 알아보았습니다.

Optional은 생성 비용이 비싸므로, 최후의 수단으로 사용하는 것이 좋다고 하네요.

이 점 공유드리며, 글을 마치겠습니다.

긴 글 읽어주셔서 감사합니다.

 

 

 

 

참고 자료

반응형
반응형


<< EC2 HTTPS로 연결하기 >>

 

1편) 도메인 구매하고 ACM 인증서 발급하기 (링크)

(도메인 구매 --> 도메인 인증 --> ACM 인증서 발급 --> Target Group 생성 --> Load Balancer 생성 --> 규칙 수정 --> Health check 성공)

 

2편) 로드 밸런서 사용하고 Health check 통과하기 <-- 현재 글

(도메인 구매 --> 도메인 인증 --> ACM 인증서 발급 --> Target Group 생성 --> Load Balancer 생성 --> 규칙 수정 --> Health check 성공)


개요

EC2HTTPS로 연결하기 위한 포스팅 시리즈입니다.

이전 포스팅에서는 (링크)

'가비아'에서 도메인을 구매하고,

AWS(Route 53)에서 도메인 소유를 인증하고,

ACM(AWS Certification Manager)를 통해 SSL(TSL) 인증서를 발급받았습니다.

 

이번 포스팅에서는,

EC2 인스턴스의 로드 밸런싱을 위한 타겟 그룹을 생성하고,

로드 밸런서(Load Balancer)리다이렉트 규칙을 설정하고(http 요청을 https로 리다이렉트),

로드 밸런서Health check를 통과해 로드 밸런싱을 안전하게 유지하는 방법을 정리해보겠습니다.

 

 

 

 


사전 설명 (타겟 그룹? 로드 밸런서?)

로드 밸런서(Load Balancer)는 본래, 요청을 여러 서버로 분산시키기 위해 사용하는 장치 혹은 기술입니다.

타겟 그룹을 여러 개 생성하고, 들어오는 요청을 특정 알고리즘에 따라 각 타겟 그룹에 분산시켜 보내는 것입니다.

(설명이 틀렸을 수 있으니 참고만 해주세요.)

 

하지만 지금 우리가 구현하려고 하는 방식은 아래와 같습니다.

- 타겟 그룹이 1개

- HTTPS 요청 (리스너에서 캐치) --> HTTPS를 거친 후, 로드밸런싱을 통해 본래 사용하던 HTTP 포트(타겟 그룹)로 요청

- HTTP 요청 (리스너에서 캐치) --> 위의 HTTPS로 리디렉션(by 리스너 규칙)

 

보다 정확한 로드 밸런서에 대한 이해는 아래 아티클을 추천드립니다.

https://inpa.tistory.com/entry/AWS-📚-ELB-Elastic-Load-Balancer-개념-원리-구축-세팅-CLB-ALB-NLB-GLB

 

[AWS] 📚 ELB(Load Balancer) 개념 원리 & 사용 세팅 💯 정리

ELB (Elastic Load Balancer) ELB(Elastic Load Balancer)란 애플리케이션 트래픽을 여러 대상에 자동으로 분산시켜 안정적인 AWS서버 환경을 운용하는데에 도움을 주는 서비스다. EC2뿐만 아니라 컨테이너(ECS), A

inpa.tistory.com

 

 

 

 

준비 사항

아래 내용을 따라가실 때는, 다음 사항을 기억해주셔야 합니다.

 

1. 네트워크는 연결할 EC2의 설정을 따를 것(VPC 등)

2. 보안그룹은 연결할 EC2의 설정을 따를 것

3. 나의 웹 서버에서 사용하는 포트 번호를 사용할 것 --> 본 포스팅에서는 8080

 

또한, EC2에서 사용하는 보안 그룹을 아래와 같이 수정해주셔야 합니다.

연결할 EC2 인스턴스의 정보 탭에 들어가서, 사용 중인 보안 그룹(Red box)을 클릭합니다.

 

인바운드 규칙 편집을 클릭합니다.

 

8080(웹 서버에서 사용 중인 포트 번호 / 아래에서는 설명 생략), 443번 포트에 대해 Anywhere-IPv4, Anywhere-IPv6를 모두 등록하고, 우측 하단의 규칙 저장을 클릭합니다.

 

HTTP, HTTPS 모든 요청을 열어주겠다는 것입니다.

 

이제 아래에서, 본격적으로 로드 밸런싱을 설정해보겠습니다.

 

 

 

 

타겟 그룹(Target Group) 생성

EC2 메뉴 하단에 대상 그룹이 있습니다. 클릭해줍니다.

 

타겟 그룹을 생성합니다.

갑자기 왜 영어가 된 지는 모르겠지만, 해석 + 버튼 위치 등으로 잘 따라와주시길 바랍니다.

 

타겟 유형은 인스턴스를 선택하고,

 

적당한 이름을 설정하고, 포트 번호를 바꿔주고, VPCEC2와 동일하게 설정합니다. (default VPC면 default 그대로)

 

Health check는 타겟 그룹의 EC2 인스턴스가 건강한지 확인하는 방법을 말합니다.

Unhealthy하다면, 로드 밸런서가 해당 타겟 그룹으로 요청(부하)을 보내지 않습니다.

 

이건 이따 손 볼 예정이니, 디폴트를 유지하겠습니다.

다음 페이지로 넘어가겠습니다.

 

사용할 인스턴스를 체크하고, 포트 번호를 확인한 후 아래에 포함시킵니다.

 

위처럼 타겟이 추가되었다면, 타겟 그룹 생성(Create target group)을 클릭합니다.

 

위와 같이 TG(Target Group)가 생성되었음을 확인할 수 있습니다.

 

 

 

 

로드 밸런서(Load Balancer) 생성

로드 밸런서 생성을 클릭합니다. (제 LB는 이미 생성되어있지만 무시해주세요.)

 

ALB를 사용하겠습니다.

 

적당한 이름을 설정하고 넘어갑니다.

 

네트워크 매핑은, 최소 2개의 AZ(Available Zone)을 설정해야 합니다..만

반드시 확인하셔야 할 것은, EC2가 사용하는 VPC, 서브넷과 매핑되어야 한다는 것입니다.

 

헷갈리신다면, 아래처럼 EC2 인스턴스 정보를 확인하셔서

인스턴스의 VPC, 서브넷(혹은 가용 영역)을 포함하도록 네트워크 매핑을 진행해주세요.

 

참고로 위에서 Scheme 설정을 Internet-facing으로 정했기에,

Private subnet은 매핑할 수 없습니다.

Public subnet으로 매핑해주세요.

 

보안 그룹은 EC2 인스턴스와 동일하게 설정해줍니다.

 

HTTP 8080, HTTPS 443 에 대한 리스너를 생성합니다.

Forward to를 위에서 생성한 타겟 그룹으로 설정합니다.

 

이전 포스팅에서 생성한 인증서를 적용해줍니다!!

 

Load Balancer가 생성되었습니다.

 

 

 

 

도메인 레코드 생성

이전 포스팅에서,

Route 53을 통해 생성한 호스팅 영역의 레코드 4개(A, NS, SOA, CNAME)

A는 다음에 만들 것이라고 했었는데요,

 

A 레코드를 만들어보겠습니다.

 

레코드 이름(서브 도메인)은 사용해도 되고, 안해도 됩니다. 저는 안하고 넘어가겠습니다.

트래픽 라우팅 대상을 위 그림처럼 설정해서, 위에서 생성한 LB를 지정해주어야 합니다.

그리고 레코드를 생성합니다.

 

자, 이제 호스팅 영역의 레코드 4개가 다 생성되었습니다!

 

 

 

 

로드 밸런서 리스너 규칙 추가

위에서 생성한 로드 밸런서의 정보로 들어가, 아래의 리스너 탭을 확인해봅시다.

 

443, 8080의 리스너가 존재하고, 443에는 SSL 인증서도 연결되어 있음을 알 수 있습니다.

각각을 클릭해 규칙을 편집해보겠습니다.

 

먼저 HTTPS:443 입니다.

위와 같이 설정하고 저장합니다.

요청 시에, 위에서 생성한 대상 그룹으로 100% 보내겠다는 뜻입니다.

 

이번에는 HTTP:8080 입니다.

 

 

위와 같이 규칙을 추가합니다.

 

다시 LB리스너 탭을 확인하면, Rules가 위와 같이 변경되었음을 확인할 수 있습니다.

 

자 이제 진짜 다왔습니다!

 

 

 

 

Health check

EC2>로드 밸런싱(좌측 메뉴)>대상 그룹 에서, 위에서 생성한 대상 그룹을 선택합니다.

그럼 위와 같이 대상 그룹의 각 대상들이 healthy 혹은 unhealthy 상태인 것을 확인할 수 있습니다.

 

지금의 목표는 8080healthy하게 만드는 것입니다.

그를 위해 방법만 알려드리고 포스팅을 마치고자 합니다.

 

웹 서버가 EC2 인스턴스 위에서 구동되고 있어야 합니다.

그럼 AWS에서 특정 주소로 요청을 보내고, 그 응답을 확인하여 healthy 상태를 확인합니다.

 

그 설정을 확인해보겠습니다.

편집 창으로 이동합니다.

 

여러 가지 설정이 있는데, 잘 읽어보시고 수정할 수 있습니다.

하지만 가장 중요한 것은, Health check path와 Success codes 입니다.

/로 되어 있다는 것은, root 디렉토리로 요청을 보낸다는 것인데요,

 

저의 경우를 예시로 들면,

http://domain.com:8080/ 으로 HTTP 요청을 보낸다는 것입니다. (아마 GET 요청인 것 같습니다.)

이 요청에 대한 응답 상태 코드가 200번이면, 해당 타겟을 healthy하다고 판단하는 것입니다.

 

저는 Spring boot 프로젝트로 웹 서버를 만들었기 때문에,

루트 디렉토리에 GET 요청을 보내면 404 코드가 오는 상황이었습니다.

따라서 Health check path를 특정 주소로 수정하고,

특정 주소에 대한 요청을 무조건 200으로 보내는 API를 작성하여

웹 서버 코드를 다시 빌드하고 구동시켰습니다.

 

요약하면, <타겟 주소>:8080/<Health check path> (eg. http://domain.com:8080/home/health/) 로 보낸 요청에 대한 응답이 Success codes에 포함되면 healthy 상태가 됩니다.

 

이렇게 8080 포트 타겟을 healthy 상태로 만들면,

긴 여정이 끝났습니다!

 

 

 

 

확인

확인해보겠습니다.

 

https://domain.com --> 웹 서버 주소로 연결

여기서 https의 기본 포트는 443이므로, 포트를 생략하면 https://domain.com:443 으로 연결됩니다.

domain.com:443 으로 들어온 요청은 443에 해당하는 리스너가 잡아서 인증을 거친 후 domain.com:8080(타겟 그룹)으로 보냅니다.

http 8080으로 요청을 보내도, https리다이렉트 되는 것을 확인할 수 있습니다.

 

 

p.s. https://google.com:443 은 구글로 연결되지만, https:google.com:444는 연결되지 않습니다. 

마찬가지로 http://google.com:80은 구글로 연결되지만, http://google.com:81은 연결되지 않습니다.

https, http의 기본 포트 번호가 각각 443, 80이고, 이는 자동으로 생략되기 때문에

https://google.com:443 이 아닌 https://google.com으로 표시되고, 사용할 수 있는 것입니다.


이렇게 EC2 위에 구동된 서버에 접근하는 요청을

https로 연결하는 긴 여정이 끝났습니다.

 

두 편의 포스팅에 걸쳐 우리가 한 작업은 아래와 같습니다.

- 가비아에서 도메인을 구매

- 호스팅 영역을 생성

- 도메인의 네임서버를 AWS 호스팅 영역의 것으로 수정

- ACM에서 SSL 인증서를 발급

- 로드 밸런싱 타겟 그룹을 생성(EC2 인스턴스 IP 주소의 8080번 포트)

- 로드 밸런서를 만들어 https://domain.com의 443, http://domain.com의 8080번 포트에 대한 리스너를 추가

- 리스너 규칙을 추가해 http 요청을 https로 리다이렉트

- Health check 통과용 API를 만든 후, 타겟 그룹의 Health check 설정을 수정해 healthy 상태로 만들기

 

처음엔 길고 복잡하지만, 고작 2번 작업을 통해 익숙해지니

1시간이면 충분한 작업이 되었네요.

덕분에 Load balancer에 대한 이해도 깊어졌습니다.

 

 

질문이나 지적은 언제든 환영입니다.

긴 글 읽어주셔서 감사합니다!

 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts