JPA 사용 시 주의할 점

영속 상태 보장

JPA Entity는 영속상태인가, 아닌가에 따라서 동작 상 제한사항이 있을 수 있다.

예를 들면, 영속 상태의 entity는 delete가 가능하지만 비영속 상태의 entity는 불가능하다.

특정 로직을 수행하는 경우 영속 상태를 인지하고 있어야한다.

@Transactional
public void deleteQuestion(User loginUser, Question question) throws CannotDeleteException {

    if (question.isDeleted()) {
        throw new CannotDeleteException("이미 삭제된 질문입니다.");
    }

    if (!question.isOwner(loginUser)) {
        throw new CannotDeleteException("질문을 삭제할 권한이 없습니다.");
    }

    List<Answer> answers = question.getAnswers();
    for (Answer answer : answers) {
        if (!answer.isOwner(loginUser)) {
            throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다.");

    List<DeleteHistory> deleteHistories = new ArrayList<>();
    question.setDeleted(true);
    deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()));
    for (Answer answer : answers) {
        answer.setDeleted(true);
        deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now()));
    }
    deleteHistoryService.saveAll(deleteHistories);
}

위의 코드는 질문에 쓰여진 답변을 삭제하는 서비스 메소드이다.

메소드 명은 deleteQuestion 이며 매개변수로 Question 을 넘겨주고 있다.

entity를 삭제하기 위해서는 넘겨받는 Question 이 반드시 영속 상태여야 하지만

코드 사용자 측에서는 실수로 비영속 상태의 Question 을 넘겨 버릴 가능성이 있다.

Question 대신 question_id를 넘기도록 수정하면 영속 상태를 보장할 수 있다.

@Transactional
public void deleteQuestion(long loginUserId, long questionId) throws CannotDeleteException {

    Question question =
        questionRepository.findById(questionId)
                            .orElseThrow(() -> new CannotDeleteException("질문을 찾을 수 없습니다."));

    if (question.isDeleted()) {
        throw new CannotDeleteException("이미 삭제된 질문입니다.");
    }

    User loginUser =
        userRepository.findById(loginUserId)
                        .orElseThrow(() -> new NotFoundException("사용자를 찾을 수 없습니다."));

    if (!question.isOwner(loginUser)) {
        throw new CannotDeleteException("질문을 삭제할 권한이 없습니다.");
    }

    List<Answer> answers = question.getAnswers();
    for (Answer answer : answers) {
        if (!answer.isOwner(loginUser)) {
            throw new CannotDeleteException("다른 사람이 쓴 답변이 있어 삭제할 수 없습니다.");
        }
    }

    List<DeleteHistory> deleteHistories = new ArrayList<>();
    question.delete();
    deleteHistories.add(new DeleteHistory(ContentType.QUESTION, question.getId(), question.getWriter(), LocalDateTime.now()));
    for (Answer answer : answers) {
        question.deleteAnswer(answer);
        deleteHistories.add(new DeleteHistory(ContentType.ANSWER, answer.getId(), answer.getWriter(), LocalDateTime.now()));
    }
    deleteHistoryService.saveAll(deleteHistories);
}

의존성 사이클

의존성 세미나 자료

https://youtu.be/dJ5C4qRqAgA

[수정본] 우아한 객체지향


의존성 사이클에 관한 문제는 JPA만의 문제점은 아니다.

다만 JPA에서 다대일 관계를 표현하면 양방향으로 의존하는 소스 코드가 간혹 나타나기 때문에 함께 적었다.

객체 참조를 통해 더 객체지향적인 코드를 작성할 수 있지만 결합도가 크게 상승하여 문제가 될 수 있다.

질문과 답변의 예시를 살펴볼건데, 의존성은 아래처럼 순환하고 있다.

AnswerQuestionQuestionAnswersAnswer → ...

여기서 QuestionAnswersList<Answer> 를 일급 콜렉션으로 포장한 클래스이다.


@Entity
public class Answer extends BaseEntity implements Serializable {

    private static final long serialVersionUID = 951549577740790162L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "writer_id", foreignKey = @ForeignKey(name = "fk_answer_writer"))
    private User writer;

    @ManyToOne
    @JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_answer_to_question"))
    private Question question;

    @Column(columnDefinition = "LONGTEXT")
    private String contents;

    @Column
    private boolean deleted = false;
}

@Entity
public class Question extends BaseEntity implements Serializable {

    private static final long serialVersionUID = -5316964078122252034L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;

    @Column(length = 100)
    private String title;

    @Column(columnDefinition = "LONGTEXT")
    private String contents;

    @ManyToOne
    @JoinColumn(name = "writer_id", foreignKey = @ForeignKey(name = "fk_question_writer"))
    private User writer;

    @Embedded
    private QuestionAnswers questionAnswers;

    @Column
    private boolean deleted = false;
}

@Embeddable
public class QuestionAnswers implements Serializable {

    private static final long serialVersionUID = 8457250053092405727L;

    @OneToMany(mappedBy = "question")
    @Where(clause = "deleted = 0")
    private final Set<Answer> answers = new HashSet<>();
}

사이클 관계를 이루는 구조에서 하나의 클래스만 수정해도 다른 클래스에 전부 영향이 가게 된다.

예를 들어 QuestionAnswersSet<Answer>List<Answer> 로 교체한다고 하면 AnswerQuestion 에서 참조하는 부분을 모두 수정해주어야 한다.

@Entity
public class Answer extends BaseEntity implements Serializable {

    private static final long serialVersionUID = 951549577740790162L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "writer_id", foreignKey = @ForeignKey(name = "fk_answer_writer"))
    private User writer;

    @Column // Question -> question_id
    private Long questionId;

    @Column(columnDefinition = "LONGTEXT")
    private String contents;

    @Column
    private boolean deleted = false;
}

Answer 의 필드였던 QuestionquestionId 로 바꿔주면서 사이클이 완전히 끊겼다.

필드 참조를 통해 얻어갈 수 있던 객체지향적인 장점은 잃게 되지만 결합도를 낮춰 코드 수정 위험도를 줄일 수 있다.

'Java > JPA' 카테고리의 다른 글

[JPA] 다양한 연관관계 매핑  (0) 2020.04.07
[JPA] 연관관계 매핑 기초  (1) 2020.02.26
[JPA] 엔티티 매핑  (0) 2020.02.22
[JPA] EntityManager, 영속성 컨텍스트  (0) 2020.02.17

ATDD

ATDD를 간단하게 소개해보자면...

  • 인수 테스트는 고객과 개발자, 그리고 테스터간의 커뮤니케이션을 기반으로 한 개발 방법론이다. 이러한 프로세스는 개발자와 테스터가 구현 전에 고객의 요구를 이해하는 데 도움이 되며 고객이 자신의 도메인 언어로 대화 할 수 있도록 한다.

Trade off of TDD

테스트 케이스가 없는 코드는 깨끗한 코드가 아니다. 아무리 코드가 우아해도, 아무리 가독성이 높아도, 테스트 케이스가 없으면 깨끗하지 않다.

  • 테스트는 코드 품질을 일정 수준까지 보장할 수 있는 가장 확실한 수단
  • TDD는 개발보다는 테스트를 우선으로 개발하는 방법론
  • TDD를 마냥 진행하기에는 몇 가지 어려운 문제점이 있다.
    • TDD는 실패한 테스트부터 작성해야 한다. 어떻게 시작하고 어디까지 구현해야 하는가?
    • 막상 TDD로 구현을 하고 나니 쓰지 않는 코드가 완성되었다.
    • 어렵사리 완성을 했지만 요구사항이 변경되며 테스트 코드까지 대거 변경해야 한다. 어디부터 다시 TDD를 시작해야 할까?
    • TDD로 개발하니 어디부터 구현해야 하는지 감이 잘 오지 않아 생산성이 저하되었다.

ATDD 는 이 고민에 대한 해결책이 될 수 있다. 인수 테스트는 작업의 시작과 끝이 명확한 테스트이기 때문에 생산성에 큰 도움이 된다.

ATDD 개요

ATDD Cycle

  1. 사용자 시나리오 설계
  2. 작업 대상의 요청값응답값 설정
  3. 위의 절차를 토대로 ATDD 작성
    • 작성된 테스트는 Black box 방식이기 때문에 실제로 어떻게 구현하느냐는 고려 대상이 아니다.
    • 요청값과 응답값만을 확인하여 전 구간을 검증
  4. 작성된 ATDD를 망가뜨리지 않는 선에서 기능과 단위 테스트를 작성

인수 테스트의 조건

  1. 인수 조건이 명확해야 한다.
    • 인수 조건은 개발자가 소프트웨어의 의도를 명확하게 이해했음을 드러내는 수단이다.
    • 인수 조건의 예시
  2. 실제 요청/응답 환경과 유사한 테스트 환경이 구축되어 있어야 한다.

ATDD의 장점

  1. 테스트 의도를 더 명확하게 전달할 수 있어 구현 대상에 대한 이해가 빠름.
  2. 작업의 시작과 끝부터 정한 뒤 Top-down 방식으로 코드를 작성하는데 도움이 됨.
  3. 테스트를 최대한 수정하지 않으면서 리팩토링하기에 수월함.

ATDD 도구 소개

특징

  1. 이해하기 쉬운 구조로 단순화된 API

     given().
         param("key1", "value1").
         param("key2", "value2").
     when().
         post("/somewhere").
     then().
         body(containsString("OK"));
  2. 검증을 위한 다양한 방식의 API 지원

     // Example with JsonPath
     String json = get("/lotto").asString();
     List<String> winnerIds = from(json).get("lotto.winners.winnerId");
    
     // Example with XmlPath
     String xml = post("/shopping").andReturn().body().asString();
     Node category = from(xml).get("shopping.category[0]");

프로젝트에 추가하기

gradle 기준으로 작성

  1. rest-assured

    dependencies { 
        testImplementation 'io.rest-assured:rest-assured:3.3.0' 
    }
  2. spring-restdocs

    dependencies { 
        testImplementation 'org.springframework.restdocs:spring-restdocs-restassured' 
    }

프로젝트에 더 맞는 의존성을 불러와서 사용하면 된다.

예제 코드

dhmin5693/ATDD-product-example

AWS 네트워크 구성

VPC (Virtual private cloud)

공식 문서

AWS 상에서 내가 사용할 클라우드 자원의 네트워크를 직접 구성하는 서비스이다.

네트워크 망 구성도
VPC 구성도

일반적인 네트워크 망은 수많은 물리적 장비를 직접 설치하고 구성 및 구축까지 해야하지만

VPC는 클라우드 서비스이므로 물리적 장비를 직접 설치할 필요가 없다.

망의 논리적인 구조를 웹 콘솔에서 관리하고 네트워크 정책을 설정할 수 있다.

관련 서비스로는 Subnet, Internet gateway, Route table, Security group 등이 존재한다.

VPC 생성

AWS의 VPC 메뉴로 접속하여 생성 버튼을 누른다.

적절한 이름을 생성하고 IP 주소는 CIDR 주소로 입력한다.

미리 정의된 사설 IP 대역(참고)을 활용하여 적절한 C 클래스의 주소를 넣어보자.

C 클래스의 경우 서브넷마스크가 255.255.255.0 이기 때문에 이 VPC 망에서 사용 가능한 호스트는 256개이다.

(왜 256개인지 이해되지 않는다면 CIDR 표기법을 다시 공부해야 한다)

참고로 여기에 입력하는 IP 주소는 private 주소가 되며 VPC 망 내에서만 인식이 가능하다.

public IP 주소는 AWS가 직접 관리하기 때문에 사용자가 컨트롤할 수 없다.

이번 실습에서는 10.20.30.0/24 대역을 사용한다.

서브넷

서브넷마스크와 유사한 서비스이다.

할당된 네트워크를 더욱 작은 단위로 쪼개어 효율적으로 망을 구성할 수 있도록 돕는다.

즉, VPC가 하나의 큰 망이면 서브넷은 작은 단위의 망이라고 볼 수 있다.

위에서 만든 VPC는 최대 256개의 호스트를 갖는데, 이 안에서 더 세밀하게 망을 구성해본다.


  • 관리용으로 사용할 Subnet: 32개의 호스트
    • bastion 서버로 사용 (아래에서 따로 설명)
  • 내부망으로 사용할 Subnet: 32개의 호스트
    • Database 등 외부에 직접 노출하지 않을 인프라 구성
    • VPC 내부에서만 접근 가능
  • 외부망으로 사용할 Subnet: 64개의 호스트, 2개 구성
    • 외부에서 접속 가능한 Application 구성
    • 외부의 트래픽을 견뎌야 하기 때문에 호스트의 수를 가장 많이 할당(총 128개)
    • 리전은 서울로 선택
    • Availability Zone(가용영역, AZ)은 ap-northeast-2a, 2b, 2c, 2d가 존재
      • AZ는 데이터 센터를 의미한다.
      • 특정 AZ가 장애가 발생해도 서비스를 유지하려면 2개의 subnet을 다른 AZ로 생성하는 것이 좋다.
      • 2b는 EC2 인스턴스가 생성되지 않는다. 2a와 2c에 1개씩 구성한다.

서브넷 생성

서브넷 메뉴로 접속하면 아래와 같은 화면이 나타난다.

방금 생성한 VPC를 선택하면 아래 서브넷 설정 탭의 내용이 변경된다.

  1. 서브넷 이름은 용도에 맞게 적당히 넣어주면 된다.
  2. 가용 영역(AZ)는 한 곳에 집중되지 않게 선택한다. 특히 외부망 2개 서브넷은 꼭 다른 AZ를 선택한다.
  3. CIDR 블록이 여기서 또 나오는데, IP를 어떻게 구성할지를 먼저 생각해보자.
    1. 0번 호스트부터 관리망 32개, 내부망 32개, 외부망 64개, 외부망 64개 순으로 사용한다고 가정한다.
    2. 사용할 개수는 2의 멱승개로 구성했다. CIDR로 표기하기 위함이다.
    3. 관리망: 10.20.30.0/27
    4. 내부망: 10.20.30.32/27
    5. 외부망1: 10.20.30.64/26
    6. 외부망2: 10.20.30.128/26

서브넷마스크는 왜 /27, /26일까?

VPC의 서브넷마스크가 /24 이므로 일단 24보다 큰 숫자 중에서 골라야 한다.

/27이면 호스트 개수가 5개 비트만큼, /26이면 호스트 개수가 6개 비트만큼 할당된다.

  • 32 - 27 = 5 ⇒ $2^5$ = 32개
  • 32 - 26 = 6 ⇒ $2^6$ = 64개

32개, 64개 호스트 주소를 할당했지만 실제로 그만큼 사용할 수는 없다.

몇 개의 호스트의 쓰임새는 이미 예약되어 있기 때문이다. (이 문서에서 확인)

Bastion 서버

Bastion [ˈbæstiən]. "요새", "보루"를 뜻하는 영어 단어

오버워치에서 자리만 잘 잡으면 말도 안되는 화력을 보여주었던 이 녀석하고 스펠링이 같다.

bastion 서버는 외부의 공격으로부터 서버를 효과적으로 방어하기 위한 수단이다.

외부에서 EC2에 접속하기 위해서는 일반적으로 SSH를 사용하는데,

bastion에만 외부 SSH 접속을 허용하고 그 외 모든 서버는 bastion을 통해서만 접속할 수 있게 통제한다.

  • 장점
    • 서비스 트래픽과 관리자용 트래픽 구분 가능
    • bastion에만 보안을 집중하면 되기 때문에 효율적
    • bastion이 공격으로 인해 피해를 보면 bastion만 재구성하면 되므로 서비스 영향을 최소화할 수 있음.

Internet gateway

공식 문서

VPC에 속한 클라우드 자원이 외부 인터넷과 통신할 수 있게 만들어주는 서비스이다.

생성하고 VPC를 선택하면 끝이다.

Route table

공식 문서

라우터와 비슷한 역할을 하는 서비스이다.

라우터는 네트워크 계층에 속하는 장비로, 서로 다른 네트워크 간의 통신을 중계한다.

목적지 정보(MAC 주소)를 테이블에 가지고 있다면 forwading 시켜주고 아니면 drop한다.

Route table 생성

  1. 생성한 VPC를 선택하고 용도에 맞는 적당한 이름을 기입하여 Route table을 생성
  2. 관리망, 내부망, 외부망 전용 총 3개를 만들어도 되고, 인터넷 접근 가능 1개/불가능 1개를 만들어도 된다.
  3. 생성된 Route table 정보로 들어가면 라우팅 정보와 연결할 서브넷을 편집할 수 있다.
    • VPC 내부망끼리는 모두 통신이 가능해야 한다. 10.20.30.0/24 는 모든 라우팅에 추가한다.
    • 인터넷에 접근하려면 위에서 생성한 Internet gateway를 라우팅에 추가한다.
      • 외부망은 외부에서 application에 접근해야 하기 때문에 인터넷 통신이 필요하다.
      • 관리망은 외부에서 SSH를 통해 관리 목적으로 접근해야 하기 때문에 인터넷 통신이 필요하다.

Security group

공식 문서

AWS 클라우드 자원의 인바운드 및 아웃바운드 정책을 관리하는 방화벽 서비스이다.

Security group 생성

관리망, 외부망, 내부망 순으로 보안 그룹을 생성한다.

순서는 바뀌어도 상관이 없으나, 허용 대상을 보안 그룹으로 선택할 수 있기 때문에

순서를 맞춰서 만들면 왔다갔다 하는 귀찮음을 줄일 수 있다.

각 망의 쓰임새에 맞는 인바운드 포트만 개방하고 아웃바운드는 건드리지 않는다.

또한 보안을 위해 내 IP 혹은 특정 대역에만 포트를 열어두는 것이 좋다.

  • 관리망 (bastion)

    1. 접근이 필요하므로 SSH(22) 포트를 개방한다.

    2. (Optional) bastion 서버가 살아있는지 보기 위해 ping을 사용하려면 ICMP - IPv4을 개방한다.

  • 외부망 (application)
    1. 외부에서 HTTP 포트로 접근하여 어플리케이션을 사용한다. HTTP 포트를 모두에게 개방한다. Anywhere-IPv4, IPv6을 선택하면 된다. HTTP 기본 포트가 아니라 다른 포트를 사용한 경우 TCP를 선택하고 해당 포트를 개방한다.
    2. HTTPS까지 사용하는 경우엔 HTTPS까지 개방한다.
    3. bastion에서 SSH로 접근해야 한다. 관리망 보안 그룹을 허용 대상으로 선택하면 해당 보안 그룹에 속한 모든 자원에 공통적으로 적용된다.
    4. (Optional) 관리망 보안 그룹에 대해 ICMP 포트를 개방하여 ping을 사용할 수 있다.
    5. (Optional) 관리망 보안 그룹에 대해 telnet 포트를 개방하면 특정 포트가 살아있는지 확인할 수 있다.

'모두' 를 대상으로 개방
보안 그룹을 대상으로 선택

  • 내부망 (인프라)
    1. mysql을 사용할 예정이기 때문에 mysql 기본 포트 3306을 개방한다. 대상은 외부망 보안 그룹으로 지정한다.
    2. 기타 다른 인프라를 사용하는 경우 해당 포트를 외부망 보안 그룹에게 개방한다.
    3. bastion에서 SSH로 접근해야 한다. 이전에 설정한 관리망 보안 그룹을 선택하면 보안 그룹에 속한 모든 자원으로부터 SSH 접근이 가능하다.
    4. (Optional) 관리망 보안 그룹에 대해 ICMP 포트를 개방하면 ping을 사용하여 확인할 수 있다.
    5. (Optional) 관리망 보안 그룹에 대해 telnet 포트를 개방하면 특정 포트가 살아있는지 확인할 수 있다.
    6. (Optional) 보안 그룹 이야기는 아니지만, 구축 초기에 mysql 설치 등 이유로 인터넷에 접근이 필요할 수 있다. 그런 경우 서브넷 메뉴에 가서 내부망 그룹의 라우팅 테이블에 인터넷 게이트웨이를 임시로 추가하여 사용한다.

EC2 생성

여기서는 구성한 망을 적용하는 방법에 대해서만 다룬다.

  1. EC2 생성 메뉴로 접속한다.
  2. OS는 입맛대로 선택한다. 가급적이면 LTS인 버전이 안정적이다.
  3. 네트워크는 생성한 VPC를, 서브넷은 EC2의 용도에 따라 알맞게 선택한다.
  4. 나머지 옵션은 필요에 따라 적절하게 선택하고 생성하면 된다.

CIDR 표기법

사이더(Classless Inter-Domain Routing, CIDR)는 클래스 없는 도메인 간 라우팅 기법으로 1993년 도입되기 시작한, 최신의 IP 주소 할당 방법이다.

네트워크를 잘 모르는 사람도 IP 주소가 무엇인지, 그리고 어떻게 생겼는지는 얼추 알고 있는 경우가 많다.

CIDR 표기법은 IP 주소에 특별한 의미를 가진 숫자를 더하여 표기한다. (192.168.50.0/24 같은 방식)

CIDR을 제대로 이해하기 위해서는 IP 주소와 서브넷마스크에 대한 이해가 선행되어야 한다.

IP 주소

  • 네트워크 자산의 논리적인 주소
  • 표기법
    • 0부터 255까지의 숫자를 4개 사용
    • 192.168.0.1 와 같은 방식으로 사용
  • 영역
    • IP 주소는 Network 영역과 Host 영역을 합쳐서 쓰는 형태이다.
    • 192.168.0.1 의 예시에서
      • 192.168이 네트워크 영역이라고 가정
      • 네트워크 영역이 아닌 부분은 모두 호스트 영역이다. 여기선 뒤의 두 개 숫자가 호스트 영역이 된다.
      • 192.168.x.x 의 형태로 사용할 수 있다.
      • x는 0 ~ 255 범위이므로 192.168 네트워크 대역의 호스트 개수는 256 * 256 = 65536개이다.

네트워크 클래스

Reference

  • IP 주소를 효율적으로 사용하기 위해 정한 국제적인 규칙
  • A, B, C, D, E 클래스가 존재
  • IP 주소 중 첫 숫자를 2진법으로 변환한 뒤 시작 비트를 확인하면 클래스를 구분할 수 있다.
  1. A 클래스
    • 비트가 0으로 시작하는 숫자 (0000 0000 ~ 0111 1111 ⇒ 0 ~ 127)
    • 첫 번째 주소만 네트워크 주소이고 나머지 3개는 호스트 주소이다.
    • 124.0.0.1 는 A 클래스에 속한다.
  2. B 클래스
    • 비트가 10으로 시작하는 숫자 (1000 0000 ~ 1011 1111 ⇒ 128 ~ 191)
    • 앞의 두 숫자만 네트워크 주소이고 남은 2개는 호스트 주소이다.
    • 168.35.0.0 은 B 클래스에 속한다.
  3. C 클래스
    • 비트가 110으로 시작하는 숫자 (1100 0000 ~ 1101 1111 ⇒ 192 ~ 223)
    • 앞의 3개 숫자가 네트워크 주소이고 마지막 1개는 호스트 주소이다.
    • 200.200.0.0 은 C 클래스에 속한다.
  4. D 클래스
    • 비트가 1110으로 시작하는 숫자 (1110 0000 ~ 1110 1111 ⇒ 224 ~ 239)
    • 멀티캐스트를 위해 사용하는 주소 대역이다.
  5. E 클래스
    • 비트가 1111로 시작하는 숫자 (1111 0000 ~ 1111 1111 ⇒ 240 ~ 255)
    • 예약된 주소

A → E로 갈수록 시작 비트에 포함되는 1의 개수가 증가하는 것을 볼 수 있다.

서브넷 마스크

클래스만 사용하면 할당받은 IP 대역이 낭비되는 문제가 있다.

이러한 문제를 해결하기 위해 서브넷 마스크 주소를 사용하여 IP를 조금 더 세밀하게 나누는 기법을 사용한다.

어느 회사가 B 클래스 대역 1개를 할당받은 상황
  - B 클래스의 범위는  128.0.0.0 ~ 191.255.255.255
  - 여기서 네트워크 주소는 앞 2개 숫자를 사용한다.
  - 할당받은 대역이 130.10.x.x라고 가정
  - 앞의 두 개 숫자에 해당하는 130.10은 네트워크 영역이라 고정
  - 호스트 영역에 해당하는 x.x는 256개 * 256개 = 65536개이므로 최대 65536개의 IP를 사용할 수 있다.
  - 그러나 실제로 사용하는 IP가 10000개 뿐이라면 55536개가 낭비된다.
  1. 서브넷 마스크는 연속된 1이 먼저 나타나고 그 이후 0만 나타나도록 구성한다.
    1. 255.255.255.0 ⇒ 11111111 11111111 11111111 00000000
    2. 255.255.255.240 ⇒ 11111111 11111111 11111111 11110000
  2. 1의 개수로 네트워크 주소와 호스트 주소를 다시 나눈다. 네트워크 주소는 1이 닿는 범위까지이다.
    1. 255.255.255.0은 24개의 1을 사용
      1. 130.10.20.30 이라는 IP가 있다면 130.10.20이 네트워크 주소
    2. 255.255.255.240은 28개의 1을 사용
      1. 네트워크 주소가 앞 28개 비트, 호스트 주소가 뒤 4개 비트밖에 없다.
      2. 이 대역은 호스트 주소가 16개이다.

CIDR

CIDR을 이해하기 위한 준비가 끝났다.

CIDR은 IP주소 + 서브넷마스크를 같이 표기한 것이다.

  • 192.168.50.0/24 의 예시
    • /24 는 앞의 24개 비트를 네트워크 영역으로 사용하겠다는 의미이다.
    • 따라서 192.168.50이 네트워크 주소이고 192.168.50.0 ~ 255 범위 내에서 호스트를 사용한다.
  • 192.168.30.32/28
    • 서브넷마스크가 8의 배수로 나누어 떨어지지 않아 계산이 조금 난감할 수 있다.
    • IP 주소의 전체 비트 수는 32개이고 28개 비트가 네트워크 영역이므로 호스트는 2^4 = 16개이다.
    • 시작 호스트는 32부터이다. 즉, 사용할 수 있는 IP는 192.168.30.32 ~ 192.168.30.47 이 된다.

잘못된 정보에 대한 지적 및 내용 보충은 언제든지 환영합니다.

업계의 상반기 동향

개발자라면 상반기 업계 동향에 대해 고민하지 않을 수 없는 시기였다.

이유라면 당연히...

개발자 연봉 대란

동요하지 않은 사람이 있을까 싶을 정도의 핫이슈가 업계를 강타하고 있었다.

우리 회사도 뒤늦게 동참하였고 다른 회사에 비해 크진 않지만 그래도 기분은 좋을 정도였다.

 

그래서 내 상반기 회고에 왜 동향이 나오는가 하면...

Github commit 내역

오랜 이직 준비에 지쳐버린 심신이 이 때를 기점으로 다시 살아났다.

지금 당장 나가지 않더라도 준비하지 않으면 도태될 것만 같은 위기감이 들었다.

그러면서 1월 중순부터 매일 공부를 하는 습관을 들이게 된 것이다.

첫 연속 커밋을 시작한 날은 2021년 1월 15일이었고 몇 번의 실수를 제외하면 매일 꾸준히 커밋을 진행하고 있다.

그러던 중 전사 연봉 일괄 인상안이 발표되면서 한시름 놓았는데 그 유혹이 너무 강해서 살짝 힘이 빠질뻔 했다 ㅎㅎ

그래도 꾸역꾸역 유지하려고 노력한 결과 관성이 생겨버렸다.

 

데일리 커밋에서 느낀 점

이전엔 데일리 커밋과 공부는 상관없으며 그저 쌓여가는 데이터를 위해 커밋을 한다고 생각했다.

음... 사실 아직도 맞다고 생각한다.

 

다만 그 의미가 조금 바뀌었는데,

부정적인 의미로 생각하면 오늘 뭔가 한 척 하기 위해서 대충 커밋을 반복하는 셈이 될테지만

긍정적인 의미로 받아들인다면 공부를 위한 명분이 추가된다는 것이다.

 

나는 사람이 공부를 계속 잡고 하려면 최소한의 명분이나 목표가 있어야 한다고 생각한다.

의미없는 공부는 본능적으로 안하는게 사람 아닐까?

데일리 커밋은 이런 점에서 굉장히 긍정적인 습관이 될 수 있다.

 

하루하루 데이터를 쌓아나가는게 생각보다 뿌듯한 일이었다.

맨 처음 데일리 커밋을 시작했던 "언제든 이직할 수 있는 준비"는 이젠 크게 중요하지 않다.

계속 이 좋은 습관을 이어나가는게 목표가 되었다.

 

아쉬운 점

공부하는 습관을 들이고 커밋 로그를 하나씩 쌓아나가는건 좋지만 블로그도 함께 잘 하고 싶은 마음이 컸다.

jojoldu님 같은 분과 비교해보면 이건 분명 나의 미숙함 때문이다.

매일 커밋을 하자니 블로그가 힘들고, 블로그를 자주 작성하자니 커밋이 쉽지 않은 미묘한 상충관계가 발생했다.

 

책 내용을 정리한 포스팅은 어짜피 검색하면 다 나오기 때문에 비중을 두고 싶지 않다.

나만의 정리, 나만의 컨텐츠를 만들고 싶었지만 그게 쉬운 일은 아니었다.

어찌됐던 계속 잘해보고 싶은 마음은 있으니 양질의 컨텐츠를 더 고민해보아야 할 것 같다.

 

우아한 테크캠프 2기

작년 7월엔 NextStep TDD 9기를 수강했다. 모든 미션을 완료하여 수료자 명단에 들 수 있었다.

마지막엔 번아웃이 찾아오면서 조금 늦긴 했다.

어찌됐던 모든 미션을 다 해내며 굉장히 좋은 교육이라는 인상을 많이 받았던 기억이 있었다.

각 미션은 몇 단계의 step으로 나뉘고 하나를 완성하면 다음 단계로 나아가는 방식이었는데

완료 도장을 하나씩 찍어갈 때마다 성취감이 굉장히 컸다.

교육 기획을 어떻게 했는지는 모르겠지만 gamification 이론을 살짝 섞은 것 같다.

 

여튼! 우아한 테크캠프의 커리큘럼이 굉장히 좋다는 소문이 있어서 기대하고 있었는데 5월에 마침 딱 교육과정이 열렸다.

교육을 받기 위해 긴 장문의 글을 쓰고... 프리코스 1주차... 2주차... 미션을 완료했다.

작년에 TDD를 선수강했던 경험이 많은 도움이 되었다.

 

그리고...

합격!

 

미션은 총 8개가 있고 기간은 9주 (2021년 5월 17일 ~ 2021년 7월 19일 마감) 동안 진행한다.

1등을 목표로 매일 새벽 늦게까지 공부했지만 8주차에 살짝 삐끗하면서 아쉽게도 1등은 놓치고 말았다 😭

 

교육의 주제는 굉장히 좋았다.

1주차는 TDD를 찍먹하며 객체지향에 대해 고민하는 시간을 갖는다. 찍먹이라 표현한 이유는 TDD는 별도의 강의로도 있지만 여기선 한 번의 미션만 진행하기 때문이다.

2주차는 JPA이다. 개인적으로도 조금 공부해봤고 실무에도 살짝 쓰고 있지만(부서 특성 상 insert에 사용), 아직 사용법을 잘 모르는구나를 많이 느꼈다.

ATDD (+ TDD), AWS 인프라 구성 및 성능 최적화, 레거시 코드 리팩토링 등을 이어서 진행했다.

 

열심히 공부한 결과 마지막 주차에 모든 미션을 마칠 수 있었다.

모두 완료!

 

열심히 한 결과 우수 수료자 명단에도 들 수 있었다. 😀

 

거의 하루, 못해도 2일에 1번 이상의 PR을 날렸고 실제로 이 기간동안 github 잔디의 대부분은 PR이나 리뷰에 남긴 답글이었다.

그리고 2달동안 이 이외의 공부를 할 시간이 거의 없었다.

가끔 잔디를 채우지 못하겠다 싶은 날은 아무 의미없는 야매 커밋을 한 날도 있다. 😰

내 비양심에 한 가지 변명을 해보자면 아무 공부를 하지 않은 날은 야매 커밋도 하지 않으려 했다. 매일 공부해서 그런 날이 없을 뿐이다. 🤣

열심히 공부했던 내용들이니 빨리 잊고 싶지 않다.

과제를 진행하면서 공부했던 내용이나 느낀 점을 포스팅하여 남길 예정이다.

 

끝맺음

나름 놀건 다 놀았기 때문에 마냥 놀면서 보낸줄로만 알았는데 기록으로만 보면 열심히 보낸 상반기처럼 보인다.

작년 이직을 시작으로 방향점을 잡았다면, 이번 상반기는 성장하기 위한 습관을 잘 다진 시기라고 생각한다.

이 습관을 바탕으로 더 보람찬 하반기를 보내고자 한다.

 

'기타 > 회고' 카테고리의 다른 글

주니어 개발자의 이직 이야기  (2) 2021.01.26
2020 상반기 회고  (0) 2020.07.18
2019 회고록과 새로운 다짐  (0) 2020.01.29

Mac에서는 Command + ` 키를 활용하여 활성화된 프로그램 창을 전환할 수 있다.

그러나 IntelliJ 에서는 이게 먹히지 않는다.

일부 키를 바꿔주면 간단하게 고칠 수 있다.

 

command + , 를 눌러 Preferences로 들어간다.

Next Project Window에 마우스를 대고 우클릭을 하면 할당된 키 조합인 Command + `를 삭제할 수 있다.

그리고 Activate Next Window에 Command + `를 할당한다.

 

두 기능의 키 조합을 바꿔주고 나면 창 전환이 잘 되는 것을 확인할 수 있다.

개발을 하다 보면 특정 포트가 겹쳐서 어플리케이션이 에러를 뱉는 경우가 종종 발생하고는 한다.

그럴때 포트를 사용중인지 확실하게 확인하는게 좋다.

 

lsof 명령어는 거의 대부분의 운영체제에서 사용할 수 있다고 하니 이 명령어를 활용해보자.

sudo lsof -nP -i4TCP:{PORT} | grep LISTEN

{PORT} 대신 8080처럼 확인하고자 하는 포트 번호를 입력하면 된다.

하지만 이 명령어를 매번 치기도 귀찮다.

그럴땐 쉘 스크립트의 function을 활용해보자.

port() {
    sudo lsof -nP -i4TCP:$1 | grep LISTEN
}

이 function을 ~/.bashrc~/.zshrc 등 쉘 실행과 동시에 같이 실행되는 파일에 저장한다.

port 8080과 같이 입력하여 사용하면 된다.

어플리케이션이 메시지를 얼마나 빠르게 카프카 가져가고 있는지를 측정하고 있었다.

kafka를 리눅스 쉘에서 확인하면 current-offset 필드를 확인할 수 있는데,

같은 토픽의 모든 파티션 offset을 더하였다.

그리고 1분 후에 offset을 더한 결과와 비교하는 방식으로 대략적인 분당 트래픽 처리량을 계산하였다.

 

파티션이 적었을 땐 offset을 일일히 더해줘도 큰 문제가 없었다.

받는 데이터가 많아지면서 파티션을 몇 배나 증설하니 슬슬 수동으로 더하기 귀찮아졌다.

offset은 항상 같은 컬럼에 있다는 규칙성이 있으니 한 번의 명령어로 해결하고자 하였다.

아는 명령어를 하나씩 파이프로 이어보고 모르는건 구글링을 해본 결과 아래처럼 입력하여 원하는 결과를 도출할 수 있었다.

 

ls -l | tail -n +2 | tr -s ' ' | cut -d ' ' -f2 | awk '{sum+=$1} END {print sum}'

하나씩 분석해보자.

  1. ls -l
    • ls -l의 두 번째 필드는 링크 파일의 개수이다. 이 개수를 더하려고 한다. 
    • ls -l은 예시일 뿐이다. 원하는 명령어를 여기에 입력한다. 카프카 토픽 확인 명령어를 이 위치에 입력했다.
  2. tail -n +2
    • ls -l의 첫 번째 줄은 total이고 두 번째 줄부터 파일 목록이 출력된다.
    • 불필요한 줄은 무시하고 출력하기 위한 옵션이다.
  3. tr -s ' '
    • 각 필드마다 탭으로 구분이 되어 있다. 탭 대신 스페이스바 한 칸으로 구분하도록 변경한다.
    • 다음 명령어인 cut에서 구분자를 인식하기 위해 변경했다.
  4. cut -d ' ' -f2
    • cut은 필드를 뽑아내는 명령어이다.
    • -d는 구분자를 정하는 옵션으로, 기본값은 탭이다. 하지만 -d 없이 원하는 결과가 나오지 않아 tr 명령어를 먼저 사용했다.
    • -f2는 구분자를 기준으로 2번째 필드만을 뽑아낸다. 각 줄마다 파일의 링크 개수가 뽑혀나온다.
  5. awk '{sum+=$1} END {print sum}'
    • awk 명령어를 활용하여 덧셈 및 출력을 하게 만들었다.

 

아래 사진은 mac에서 /bin 디렉토리에 있는 파일들의 용량을 합한 결과이다.

 

다양한 방법으로 활용할 수가 있는데, 조금 변형하여 function으로 지정한 뒤 Kafka lag 남은 개수를 본다던가 하는 방법이 있다.

+ Recent posts