Intro


나무가 아닌 숲을 바라보자.

두 달간의 교육과정 속에서 기술적인 내용에 흠뻑 빠져서 쉴틈 없이 재미를 느끼기도 하지만 가끔 그 껍데기를 깨고 나오지를 못할 때가 있다. 그렇게 되면 그 내용이 맞든, 틀리든 내 주관적인 생각을 가지고 판단하게 된다.

특히나 이번 12월 한달 동안은 이러한 교만과 자만을 가지고 공부했지 않았나 반성하게 된다. 예를 들어 하나의 CS 지식을 공부하기 위해 다른 개발자분들의 글을 살펴볼 때 공식 문서가 아니라는 이유로, 내용이 부실하다는 이유로, 학습 수준의 기록이라는 이유로 나도 모르게 비교를 했던 것 같다.

그렇게 비교를 하니 방문자 수가 많은 분들의 글만 신뢰하고, 유명한 분들의 글만 찾아보게 되는 아주 나쁜 행동을 하고 있었다. 지금와서 돌아보니 내가 무슨 자격으로, 무슨 염치로 다른 개발자분들을 판단하고 기준을 세웠는지 모른다.

이러한 증상을 인지하고 충격을 받아 저런 쓸데없는 생각은 바로 버렸다. 그들을 지식의 질이나 양으로 판단하지 말고 겸손한 자세를 다시 갖추기로 하였다. 물론 누가봐도 잘못되었거나 오기가 기록된 글이라면 정정해야 하는 것이 맞지만, 단순히 글의 질이나 양을 가지고 나의 주관을 들이밀지 말자고 다짐했다.

주어진 시간에 효율적인 공부를 해야 하는 나는 물론 잘 정리된 글, 실력이 뛰어난 사람의 글을 보는 것이 경험치를 더 쌓을 순 있겠지만 나와 같거나 비슷한 수준의 돌료들은 개발자로 어떻게 성장해 나가는지 찾아보는 것도 굉장히 중요하다고 느꼈기 때문이다.




Day - 39

Lambda Expression(람다식)이란?

람다 표현식(lambda expression)은 메소드를 하나의 식으로 표현한 것이다. 메소드를 람다식으로 표현하면 메소드의 이름이 필요 없기 때문에 람다식은 익명 함수(Anonymous Function)의 한 종류라고 볼 수 있다.

람다식은 어떻게 등장한 것인가?

먼저 람다식은 어떻게 만들어졌는지 알아보자.

Java 8부터 람다식(Lambda Expression)과 스트림을 지원하기 시작했는데 어떻게, 어떤 이유로 추가되었는지 궁금해졌다.

Java에서는 함수형 프로그래밍 기법을 사용하도록 람다와 스트림 등의 문법들을 추가했다고 한다. 여기서 람다와 스트림 등의 추가된 문법에 집중하기보다 함수형 프로그래밍에 초점을 두어야 한다고 느꼈다.

Java를 통해 거의 대부분은 객체지향 프로그래밍 위주의 문법이나 기법들을 접하게 되는데 거기에 추가로 함수형 프로그래밍 요소를 함께 활용할 줄 알는 개발자가 되어야 한다는 것이다.


말 보다는 코드로 직접 비교해보자. 먼저 일반적인 메소드를 작성해보았다.

int addPoint(int point, int getPoint) {
	return point + getPoint;
}

위 메서드를 람다식으로 고쳐 작성하면 다음과 같다.

(point, getPoint) -> point + getPoint;

이렇게 람다식을 이용하면 불필요한 코드를 줄이고, 가독성을 높일 수 있다.

그렇기 때문에 함수형 인터페이스의 인스턴스를 생성하여 함수를 변수처럼 선언하는 람다식에서는 메소드의 이름이 불필요하다고 여겨져서 이를 사용하지 않는 대신 컴파일러가 문맥을 살펴 타입을 추론한다고 한다.

어떤 상황에서 람다식을 활용할 수 있을 지 몇가지 예제를 작성해보자.

forEach문에 람다식 적용하기

List 객체를 순회할 때 람다식을 활용해 객체의 요소들에 접근할 수 있다.

List<String> list = new ArrayList<>();
list.add("통기타");
list.add("일렉기타");
list.add("베이스기타");

// forEach문에 람다식 적용하기
list.forEach((temp) -> {
	System.out.println(temp);
});

// --- 출력문 ---
// 통기타
// 일렉기타
// 베이스기타

하나의 List를 forEach 구문을 통해 순회하는데, 람다식을 활용해 순회하며 출력하는 코드이다.

물론 foreach문의 제약으로 따로 반복횟수를 명시하지 못하거나, 순차적으로 반복될 때만 사용이 가능하지만, 기존의 for문 구조와 동일하게 가져간다고 할 때 더 간결하게 작성할 수가 있음을 알 수 있다.

람다식을 활용해 정렬하기

String[] brands = {"Samsung", "Apple", "LG", "Sony"};

위 문쟈열 배열을 정렬할 때도 람다식을 요긴하게 사용할 수 있다.

Arrays.sort(brands, new Comparator<String>() {
  	@Override
  	public int compare(String o1, String o2) {
  		return o1.compareTo(o2);
  	}
});

Comparator 인터페이스 구현하여 compare 메서드를 재정의하여 오름차순 정렬을 진행 할 수 있는데, 위 코드를 람다식을 통해 더 간단하게 줄일 수 있다.

Arrays.sort(brands, (o1, o2) -> {
	return o1.compareTo(o2);
});

위와 같이 간단하게 작성할 수 있지만, Comparator 인터페이스를 구현한다는 느낌을 코드에서 찾아볼 수 없어 가독성이 저하된다고 생각이 들었다.

람다식을 무조건 사용해야 할까?

아이러니하게도 람다식을 사용하는 이유 중 하나가 복잡한 코드를 줄여서 개발자의 의도를 명확하게 전달하거나 파악하기 위해서 람다식을 이용한다고 하지만, 오히려 코드의 의미가 무엇인지 단번에 이해하기 힘들게 될 수도 있다는 느낌을 받았다.

람다를 알고 있는 개발자에게는 이해하기 쉽겠지만 람다를 모르는 개발자라면 이해할 수 있을까? 라는 생각이 들었다. 결국 함수형 프로그래밍이 대세라고는 하지만 그렇다고해서 람다식이 무조건 옳은 것은 아니라고 판단하게 되었다. 늘 그렇지만 상황에 맞게, 적재적소에 필요한 방식을 적용하는 것이 개발자로서 바람직한 자세가 아닐까 생각한다.



Day - 40

::(이중 콜론 연산자) 은 뭘까?

Java로 lamda를 배우면서 ::을 보게 되었다. ::은 메서드 이름을 매개변수로 전달하고 싶을 때 코드를 작성하지 않고 메서드를 직접 넘기고자 할 때 사용하는데, 왜 이중 콜론 연산자를 사용하는지 궁금하여 찾아보았다.

이중 콜론 연산자 ::의 정식 명칭은 메소드 참조 표현식(method reference expression)이라고 한다. 람다 표현식(expression)에서만 사용 가능하며, 사용하는 목적은 람다식에서 파라미터 중복 없이 메서드를 넘기고자 할 때 사용한다.

::[인스턴스]::[메소드명(또는 new)] 형식으로 작성하여 사용할 수 있고 static 메소드인 경우 인스턴스 대신 클래스 이름으로 사용할 수 있다.

글로만 보는 것보단 몇 가지의 예제를 작성하여 살펴보자.

반복문에서 활용하기

List를 하나 만들어 List를 순회하며 출력하는 코드를 작성하였다.

import java.util.Arrays;
import java.util.List;

public class MethodReferenceExpressionMain {
    public static void main(String[] args) {
        List<String> articles = Arrays.asList("1번글", "2번글", "3번글");
        
		// 1. article을 2번 작성해야 한다.
        articles.forEach(article -> System.out.println(article));
        
        // 2. article 작성 필요없이 :: 만 사용하여 출력할 수 있다.
        articles.forEach(System.out::println);
    }
}
// Output
// 1번글
// 2번글
// 3번글
// 1번글
// 2번글
// 3번글

첫번째 출력문을 살펴보면 forEach 내의 람다 표현식에서 article을 매개변수로 넘길 때 2번 사용하게 된다.

이 코드를 ::를 사용하여 더 간단하게 수정할 수 있다. 람다식에서 전달하고 받는 매개변수가 동일하다면 두 번째 출력문처럼 System.out::println으로 사용한다면 더 간단하게 출력할 수 있다.

인스턴스를 생성에 활용하기

간단한 Posts라는 클래스를 작성하였다.

public class Posts {
    private int postId;
    private String title;
    private String content;
    private int likes;

    public Posts(int postId, String title, String content, int likes) {
        this.postId = postId;
        this.title = title;
        this.content = content;
        this.likes = likes;
    }

    public int getPostId() {
        return postId;
    }

    public void setPostId(int postId) {
        this.postId = postId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getLikes() {
        return likes;
    }

    public void setLikes(int likes) {
        this.likes = likes;
    }

    @Override
    public String toString() {
        return "Posts{" +
                "postId=" + postId +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", likes=" + likes +
                '}';
    }
}

Posts는 게시글이라는 특성을 지닌 클래스인데, 모든 게시글의 likes 수의 합계를 구하는 로직은 아래와 같다.

List<Posts> list = new ArrayList<>();

list.add(new Posts(1, "제목1", "내용1", 1));
list.add(new Posts(2, "제목2", "내용2", 5));
list.add(new Posts(3, "제목3", "내용3", 0));

int sumPostAllLikes = list.stream()
	.mapToInt(Posts -> Posts.getLikes())
	.sum();
System.out.println(sumPostAllLikes);
// 6 출력

위 코드는 Posts의 인스턴스를 요소로 가지는 List 객체를 만들고, 각 Posts의 likes의 합계를 구하게 된다.

여기서 .mapToInt(Posts -> Posts.getLikes()) 코드를 이중 콜론 연산자를 이용하여 .mapToInt(Posts::getLikes)로 대치할 수 있다.

List<Posts> list = new ArrayList<>();

list.add(new Posts(1, "제목1", "내용1", 1));
list.add(new Posts(2, "제목2", "내용2", 5));
list.add(new Posts(3, "제목3", "내용3", 0));

int sumPostAllLikes = list.stream()
	.mapToInt(Posts::getLikes) // 이중 콜론 연산자 활용
	.sum();
System.out.println(sumPostAllLikes);
// 6 출력

위와 같이 이중 콜론 연산자를 통해 람다식을 더 보기 좋게 변경할 수가 있음을 알게 되었다.

심지어 인텔리제이에서는 이중 콜론 연산자로 대치할 수 있는 람다식이 있을 경우 자동으로 제안해주고 변경할 수 있는데 Replace lambda with method reference 기능을 통해 손쉽게 람다식을 이중 콜론 연산자 구문으로 변경할 수가 있다.



Maven과 Gradle

이전부터 다양한 빌드 도구들(Build Tools) 중 Maven과 Gradle를 사용해왔지만, 자세한 개념을 잘 몰랐다. 그래서 Maven과 Gradle의 개념은 무엇이고, 각각 어떠한 차이점을 지니는지 알아보고자 한다.

Maven

Maven(메이븐)이란?
Maven은 Java 전용 프로젝트 관리 도구이다. Apache(아파치) Ant의 대체제로 만들어졌으며 Apache 라이센스로 배포되는 오픈 소스 소프트웨어이다. 또한 POM(Project Object Model, pom.xml)에 기초를 두고 빌드, 테스트, 도큐먼테이션, 성과물의 배치 등 라이프 사이클 전체를 관리하는 도구이다.

Maven은 Java 프로젝트를 진행하며 다양한 라이브러리들을 관리해주는 도구이다.

빌드 중인 프로젝트, 빌드 순서, 다양한 외부 라이브러리 종속성 관계를 pom.xml 파일에 명시하여 사용하며 외부저장소에서 필요한 라이브러리와 플러그인들을 다운로드 한다음, 로컬시스템의 캐시에 모두 저장하게 된다.

POM(Project Object Model)이란?
이름 그대로 Project Object Model의 정보를 담고 있는 설정 파일이다.

Maven의 설정파일인 pom.xml에서 다운로드 받을 저장소를 설정할 때는 repositories에 외부 라이브러리의 의존성을 설정할 때는 dependencies에 설정한다.

Gradle

Gradle(그래들)이란?
Gradle은 온전한 오픈소스로 Apacahe Maven과 Apache Ant의 대체제로 만들어진 프로젝트 빌드 및 구성 관리, 테스트, 배포 도구이다. Maven에 비해 빌드 속도가 10~100배 가량 빠르다. 현재 안드로이드 앱의 공식 빌드 시스템이다.

Gradle은 Groovy 언어를 사용한 Domain-specific-language를 사용하기 때문에 설정파일을 xml파일로 사용하는 Maven보다 코드가 더욱 간결하다.

Groovy 언어란?
Groovy는 JVM 위에서 실행되는 스크립트 언어이다. Java와는 달리 스크립트 언어기에 소스코드를 그대로 실행하여 소스 코드를 컴파일 할 필요가 없다. 또한 Java와 호환되며, Java 클래스 파일을 그대로 Groovy 클래스로 사용할 수 있다. Java 문법과 유사하여 각종 빌드 관련 작업들을 관리할 수 있기 때문에 Groovy 언어를 사용하는 Gradle은 Java 개발자가 사용하기에 최고의 빌드 관리 도구라고도 한다.

Maven VS Gradle

Q1. Maven과 Gradle은 무엇이 다를까?

Maven의 경우는 XML로 라이브러리를 정의하고 사용하지만, Gradle의 경우 별도의 빌드 스크립트를 통해 사용할 어플리케이션 버전, 라이브러리 등의 항목을 설정할 수 있고, XML과 달리 Groovy 스크립트 언어를 활용해 변수선언, if, else, for 등의 로직을 구현할 수 있다.

Q2. 두 빌드 도구가 빌드(Build)할 때의 차이점은?
Maven은 고정적이고 선형적인 모델을 기반으로, Gradle은 작업 의존성 그래프를 기반으로 빌드한다고 한다. Gradle의 경우 특정한 작업의 변경점 여부를 검사하기 때문에 incremental build를 허용하며, 이미 변경된 작업에 대해서는 이후에 다시 작업이 실행되지 않아 빌드 시간이 훨씬 단축시킬 수 있다. 이로 인해 프로젝트 빌드 관리의 규모가 커질수록 빌드 성능 자체의 격차는 커질 것이라고 생각이 든다.

직접 Maven과 Gradle을 소스코드로 비교해보자.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.kakao.lango</groupId>
    <artifactId>java-build-tools</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
            </plugin>
        </plugins>
    </build>
</project>

위 xml 코드는 Maven의 pom.xml 형식이다. 그런데 이 복잡한 xml을 Gradle은 더욱 보기 편하게 관리할 수 있다.

apply plugin:'java'
apply plugin:'checkstyle'
apply plugin:'findbugs'
apply plugin:'pmd'
version ='1.0'
repositories {
    mavenCentral()
}
dependencies {
    testCompile group:'junit', name:'junit', version:'4.11'
}

위와 같이 Gradle은 복잡하고 장환한 코드를 간결하게 볼 수 있다는 것이 가장 큰 장점인 것 같다. 또한 속도나 캐시 사용 안전성까지 고려한다면, 당연하게도 Gradle로 사용하는게 이득이지 않을까?



VO와 DTO는 무엇이 다를까?

VO는 값 표현을 위한 객체이고 DTO는 데이터 교환을 위한 객체로 데이터를 저장소에서 서비스로, 서비스에서 컨트롤러로 옮겨가며 접근해야 할 때 사용한다고 알고 있다. 그런데 데이터를 위한 객체를 생성하다보면 나도 모르게 VO와 DTO를 혼동해서 사용하고 있을 때가 종종 있었다.

VO와 DTO중에서 무엇을 사용하는 것이 정답인지 많이 헷갈렸기 때문에 다시 정리해보고자 한다.

VO(Value Object)

VO(Value Object)란?
VO는 말 그대로 값을 표현하는 객체이다. DTO와는 다르게 로직을 포함할 수 있으며, 특정 값 자체를 표현하기 때문에 불변성을 보장하기 위해 생성자를 사용해야 한다.

VO에서 중요한 점은 이름이 다른 인스턴스라도 모든 속성이 같다면 같은 객체로 본다는 것이다. 하나의 예제를 작성해보자.

public class CoatVO {
    private String meterial;

    public CoatVO(String meterial) {
        this.meterial = meterial;
    }
}

CoatVO라는 VO 클래스를 하나 작성하고 CoatVO 클래스로 2개의 인스턴스를 생성하고 비교해보았다.

String meterial = "wool";

CoatVO coat1 = new CoatVO(meterial);
CoatVO coat2 = new CoatVO(meterial);

System.out.println(System.identityHashCode(coat1));
System.out.println(System.identityHashCode(coat2));

// 1586600255
// 359023572

당연하게도 두 인스턴스의 주소값을 비교하기 때문에 값이 같은 것과는 상관없이 다른 주소값을 출력하는 것을 알 수 있다. 이 때, 같은 값을 가지고 있는 지를 판단하는 메서드를 만들거나 equals()와 hashCode() 함수를 재정의한다면 값 동등성을 입증할 수 있다.

public class CoatVO {
    private final String meterial;

    public CoatVO(String meterial) {
        this.meterial = meterial;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        CoatVO coatVO = (CoatVO) obj;
        return Objects.equals(meterial, coatVO.meterial);
    }

    @Override
    public int hashCode() {
        return Objects.hash(meterial);
    }
}

CoatVO 클래스에 equals() 메서드와 hashCode() 메서드를 재정의 해보았다.

System.out.println(coat1.equals(coat2));
// true 출력

이렇게 VO는 값 자체를 나타내기 때문에 setter와 같은 객체의 변경점을 발생시킬 수 있는 메서드를 가지는 것은 안된다.

DTO(Data Transfer Object)

DTO(Data Transfer Object)란?
DTO란 순수하게 데이터를 담아 계층 간 데이터를 전달하는 객체이다. getter와 setter 메서드가 해당된다.

DTO는 getter와 setter메서드만을 가져야 한다고 하는데, 데이터를 전달하는 용도로만 사용해야 하기 때문에 다른 메서드가 필요하지 않다는 것이다.\

DTO로 사용할 하나의 예제를 작성해보자.

package kakao.study.dto;

public class PostDTO {
    private int id;
    private String title;
    private String content;
    private int likes;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getLikes() {
        return likes;
    }

    public void setLikes(int likes) {
        this.likes = likes;
    }
}

PostDTO라는 DTO 클래스를 생성하였다.

PostDTO 클래스를 이용해 값을 받는 쪽에서는 getter 메서드를 사용해 값을 꺼내서 사용하게 되는데, setter 메서드를 사용하면 데이터에 변경점이 발생할 가능성이 생긴다.

package kakao.study.dto;

public class PostDTO {
    private int id;
    private String title;
    private String content;
    private int likes;

    public PostDTO(int id, String title, String content, int likes) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.likes = likes;
    }

    public int getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    public int getLikes() {
        return likes;
    }
}

위에서 작성한 PostDTO를 setter 메서드 대신 생성자를 추가하는 방식으로 수정하였다.

이 때, setter 메서드 사용을 허용하게 되면 데이터가 변경점이 발생할 수도 있어 setter 메서드가 아닌 생성자를 통해 데이터를 삽입하도록 구현해야 한다는 점을 유의해야 한다.


사실 VO와 DTO를 구분지어 사용하는 것에 대해서 알아보며 혼용해서 사용하는 경우도 있었고, 명확하게 구분지어 사용하는 경우도 있었다. 어떻게 사용하라고 정해진 정답이 있던 것은 아니지만, 일단 어느정도 구분지어 사용할 줄 알아야 한다고 생각이 든다.



Day - 41

트리거(Trigger)에 대해서

트랜잭션은 어느정도 알고 있었지만, 트리거에 대해서는 얼핏 들어봤지만 잘 모르고 사용해 본 적도 없었다. 그래서 트리거에 대해서 알아보고 공부하려 한다.

트리거(Trigger)란?
특정 테이블에 삽입, 수정, 삭제 등의 데이터 변경 이벤트가 발생하면 자동으로 실행되는 작업을 의미한다. 트리거는 INSERT, UPDATE, DELETE와 같은 DML의 데이터 상태 관리를 자동화하는데 사용된다.

트리거는 직역하면 방아쇠라는 뜻인데, 말 그대로 방아쇠를 당기면 총알이 발사되듯이 트리거가 실행되면 특정한 작업이 수행된다. 즉, 개발자가 직접 호출하는 것이 아니라 데이터베이스에서 자동으로 호출해주는 것라고 이해하였다.

트리거의 종류

  • 행 트리거
    테이블 안의 행에 변경이 발생할 때마다 실행된다. 변경 전이나 변경 후의 행은 OLD, NEW라는 가상 줄 변수를 이용하여 읽을 수 있다.
  • 문장 트리거
    특정한 문장에 의해서 단 한번만 실행된다. 예를 들자면 INSERT, UPDATE, DELETE문에 대해서 한 번만 실행된다는 것이다.

트리거를 왜 사용할까?

특정 테이블에 대한 데이터 변경을 시작점으로 두고, 이 시작점과 관련된 작업을 자동화하여 수행시키기 위해서 트리거를 사용한다.

일반적으로는 이벤트와 관련된 테이블의 데이터가 삽입되거나 수정되거나, 삭제 될 때 수행해야 할 작업들을 자동으로 실행시키고 싶을 때 즉, 업무처리 자동화를 위해서 활용할 수 있기 때문에 중간에 개발자의 개입 없이 구현된 규칙대로 자동으로 실행된다. 이는 트랜잭션에 의해서 자동으로 특정한 작업이나 명령을 실행할 수 있어 업무처리를 자동화할 수 있다고 보면 된다.

트리거의 구성

트리거의 구성에 대해서 살펴보자.

선언부(DECLARE)
트리거의 명칭이 정의되는 부분이다.

이벤트부(EVENT)
트리거가 실행되는 타이밍,이벤트를 명시하는 부분이다.

시작/종료부(BEGIN/END)
트리거의 시작과 종료를 표현하며, BEGIN/ END가 쌍을 이룬다. 이는 다수의 실행을 제어하는 기본적 단위가 되고, 논리적 프로세스를 구성한다.

제어부(CONTROL)
기본적으로 순차적으로 처리한다. 비교 조건에 따라 블록 또는문장을 실행하고, 조건에따라 반복 실행한다.

예외부(EXCEPTION)
BEGIN/END절에서 실행되는 SQL문이 실행될 때 예외 발생 시 예외 처리 방법을 정의하는 처리부이다.

트리거를 사용할 때 고려사항은?

트리거 내에서는 COMMIT, ROLLBACK 등 TCL(트랜잭션 제어어)을 사용한다면 컴파일 에러가 발생하게 된다.

트리거 실행 중 오류가 발생하게 되면 트리거 실행의 원인을 제공한 데이터 작업에도 영향을 미치게 되기 때문에 유의하며 사용해야 한다.

예를 들어 INSERT 후 트리거 작업에서 오류가 발생할 경우 트리거 후 작업이 진행되지 않거나 INSERT가 온전히 완료되지 않아 데이터가 추가되지 않을 수 있다.


트랜잭션과 관련이 있는 트리거를 알아보게 되었는데, 트랜잭션과 동일하게 데이터에 변경점이 발생하였을 때를 기준으로 삼아 무언가를 행한다는 점에서 비슷하다고 생각이 들었다.



Day - 42

서블릿(Servlet)이란?

서블릿(Servlet)은 Servlet 클래스의 구현 규칙을 지키며 클라이언트의 요청을 처리하고 응답을 반환하는 Java의 웹 프로그래밍 기술이다.

서블릿은 Java를 사용하여 웹을 만들기 위해 필요한 기술인데, 구체적으로 설명하자면 클라이언트가 특정한 요청을 했을 때, 요청에 대한 작업을 처리하고 그에 대한 응답을 결과로 다시 전송해주는 프로그램이라고 보면 된다.

서블릿의 특징

  • 클라이언트의 요청에 대해 동적으로 작동하는 웹 어플리케이션 컴포넌트이다.
  • html을 사용하여 요청에 응답한다.
  • Java Thread를 이용하여 동작한다.
  • MVC 패턴에서 Controller로 이용된다.
  • HTTP 프로토콜 서비스를 지원하는 javax.servlet.http.HttpServlet 클래스를 상속받는다.
  • UDP보다 처리 속도가 느리다.
  • HTML 변경 시 Servlet을 다시 컴파일해야 하는 단점이 있다.

서블릿의 동작원리

서블릿의 생명 주기

  • Init() : 서블릿이 메모리에 로드될 때 한번만 호출된다. 코드가 수정된다면 다시 호출된다.
  • doGet() : GET 방식으로 데이터를 전송할 경우 호출한다.
  • doPost() : POST 방식으로 데이터를 전송할 경우 호출된다.
  • service() : 모든 요청은 service() 메서드를 통해 doXXX()메소드로 이동한다.
  • destroy() : 서블릿이 메모리에서 해제되면 호출된다.

서블릿의 동작 방식

  1. 사용자(클라이언트)가 URL을 입력하면 HTTP Request가 Servlet Container로 전송한다.
  2. 요청을 전송받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성한다.
  3. web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾는다.
  4. 해당 서블릿에서 service메소드를 호출한 후 클리아언트의 GET, POST여부에 따라 doGet() 또는 doPost()를 호출한다.
  5. doGet() or doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse객체에 응답을 보낸다.
  6. 응답이 끝나면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다.

이와 같이 서블릿을 사용하지 않고 직접 문자열 파싱등과 같은 작업을 구현하는 것은 개발자 입장에서 투자되는 시간과 비용이 커질 수 있다. 핵심 비즈니스 로직에 더 집중할 수 있도록 서블릿을 사용하는 것이 중요하다는 것을 알게 되었다.



Day - 43

템플릿 메서드 패턴(Template Method Pattern)에 대해서

템플릿 메서드 패턴(Template Method Pattern)이란 동일한 작업을 진행하는 로직에 대해서 특정 환경이나 상황에 맞춰서 확장 또는 변경을 해야 할 때 사용되는 유용한 패턴이다.

코드를 작성하다보면 반복되어 작성되는 코드들이 생기기 마련이다. 이 때, 코드의 중복을 없애거나 줄이기 위해 템플릿 메서드 패턴을 적용할 수 있다.

바로 실행 과정을 구현한 상위 클래스(추상 클래스)에서 템플릿을 제공하고 실행과정의 일부 단계를 구현한 하위 클래스(구체 클래스)에서 구체적인 로직을 작성하게 된다. 즉, 추상클래스에서 역할을 정해주면 하위클래스에서는 구현만 담당하면 된다는 것이다.

그렇게 중복될 우려가 있는 로직들을 상위클래스에 정의하여 담아두고 확장되거나 변경될 일부분의 로직들을 하위클래스에서 추상메서드를 재정의하는 방식으로 적용할 수 있다.

이제 글보다는 예제를 통해 템플릿 메서드 패턴을 알아보자. 쇼핑을 예시로 들어 코드를 작성해보려 한다.

먼저 템플릿 메서드 패턴 없이 오프라인 쇼핑과 온라인 쇼핑을 클래스로 만들어보면 다음과 같다.

public class OnlineShopping {
    public void shopping() {
        System.out.println("쇼핑할 아이템을 찾는다.");
        System.out.println("사고 싶은 아이템이 온라인 쇼핑몰에 있는지 찾아본다.");
        System.out.println("아이템을 구입한다.");
    }
}

public class OfflineShopping {
    public void shopping() {
        System.out.println("쇼핑할 아이템을 찾는다.");
        System.out.println("사고 싶은 아이템이 주위 가게에 있는지 직접 찾아본다.");
        System.out.println("아이템을 구입한다.");
    }
}

위 2개의 클래스에서 쇼핑할 아이템을 찾는다.아이템을 구입한다.라는 코드가 중복되는 것을 확인할 수 있다.

다음으로 템플릿 메서드 패턴을 이용해 위 OnlineShopping 클래스와 OfflineShopping 클래스의 코드 중복을 없애보자.

abstract class Shopping {
    protected abstract void branchShopping();

    public void shoppling() {
        System.out.println("쇼핑할 아이템을 찾는다.");
        branchShopping();
        System.out.println("아이템을 구입한다.");
    }
}

public class OnlineShopping extends Shopping {
    @Override
    protected void branchShopping() {
        System.out.println("사고 싶은 아이템이 온라인 쇼핑몰에 있는지 찾아본다.");
    }
}

public class OfflineShopping extends Shopping {
    @Override
    protected void branchShopping() {
        System.out.println("사고 싶은 아이템이 주위 가게에 있는지 직접 찾아본다.");
    }
}

Shopping이라는 추상 클래스를 하나 만들어 공통되는 로직들을 shopping 메서드로 구현하게끔 작성하였다.

또한, 온라인 쇼핑이냐 오프라인 쇼핑이냐에 따라 다르게 구현해야 하는 부분만 branchShopping 이라는 추상 메서드를 재정의하여 불필요한 코드 중복을 깔끔하게 없앨 수 있었다.

템플릿 메서드 패턴을 사용한다면?

이처럼 템플릿 메서드 패턴을 사용한다면 코드의 중복되는 부분을 없애거나 줄일 수 있었고 하위클래스의 역할을 줄일 수 있기 때문에 비즈니스 로직을 더욱 수월하게 관리할 수 있다. 그리고 손 수비게 객체를 추가하거나 확장할 수 있다.

반면에, 추상메소드가 너무 많아지면 클래스 관리가 복잡해져 추상클래스인 상위클래스와 구현클래스인 하위클래스 간의 관계가 복잡해져 가독성이 저하될 수도 있다고 생각이 들었다.






Final..

이번 주는 쉴 새 없이 Java와 Java의 웹 프로그래밍 기술들을 배우고 실습하고 고민해보는 시간을 가졌다.

분명 알고있었지만, 잘못 알고있었던 내용들도 많았고 추세가 바뀌어 알고 있던 내용들이 레거시가 된 것들도 많았다. 기술 트렌드의 변화는 내 생각보다 정말 빠르게 바뀌어가고 있음을 하루하루 깨닫곤 한다.

이제 Java 기초를 떼고 다음주부터는 본격적으로 Spirng 프레임워크에 대해서 까보고 배우게 된다.

항상 어렵지만 Spring을 사용하여 어떤 개발자로 성장할 수 있는지, 왜 Spring을 사용해야 하는지 먼저 생각해보고, 다른 개발자분들의 생각을 찾아보고, 마인드셋을 어떻게 지녀야 하는지 고민하는 시간들도 꼭 챙기도록 노력할 것이다.



혹여 잘못된 내용이 있다면 지적해주시면 정정하도록 하겠습니다.

게시물과 관련된 소스코드는 Github Repository에서 확인할 수 있습니다.

참고자료 출처

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글