← go back

Java와 아키텍처에 관한 필수 지식들


— Java 기본 원리
Java 언어의 특징에 대해
Java 버전별 특징에 대해
Java 프로그램 실행 과정에 대해
main 메서드에서 static이 생략된다면
— Collection Framework
컬렉션 프레임워크 주요 인터페이스
List와 Array에 대해
— JVM
JVM의 역할에 대해
JVM 메모리 구조에 대해
— Java 다양한 기능
Java Stream에 대해
Java Stream은 어떤 상황에서 활용될 수 있는가
병렬 처리는 언제 적용될 수 있는가
병렬 처리 조건에 해당하는 독립성이란
— 객체지향 프로그래밍
OOP 특징에 대해
SOLID 원칙에 대해
SOLID 원칙 중 리스코프 치환 원칙에 대해
SOLID 원칙 중 개방-폐쇄 원칙에 대해
— 소프트웨어 개발
애자일에 대해
클린 아키텍처에 대해
도메인 주도 설계에 대해
헥사고날 아키텍처에 대해
동기처리와 비동기처리에 대해
병렬처리는 어떤 상황에 적용될 수 있는가

Java 언어의 특징에 대해


Java는 객체지향적이고, 플랫폼 독립적이며, 안정성이 높아 다양한 환경에서 널리 사용되는 프로그래밍 언어입니다.


  1. 객체지향: Java는 완전한 객체지향 프로그래밍 언어입니다. 클래스와 객체를 기반으로 상속, 다형성, 캡슐화와 같은 객체지향의 핵심 개념을 지원합니다.

  2. 플랫폼 독립성: Java의 가장 큰 특징 중 하나는 Write Once, Run Anywhere이라는 원칙을 따르는 것입니다. 즉, Java로 작성된 프로그램은 Java Virtual Machine(JVM)이 설치된 어떤 플랫폼에서도 실행할 수 있습니다.

  3. 안정성: Java는 컴파일 시간과 실행 시간 모두에 걸쳐 강력한 오류 체크 기능을 제공하여 프로그램의 안정성을 높입니다. 또한, 자동 메모리 관리 기능인 가비지 컬렉션을 지원하여 메모리 누수 문제를 방지합니다.

  4. 멀티스레딩: Java는 내장된 스레딩 기능을 제공하므로 병렬 처리를 통해 프로그램 성능을 향상시킬 수 있습니다.

  5. 네트워크 지원: Java는 TCP/IP와 같은 저수준 네트워크 지원부터 HTTP, FTP 등의 고수준 프로토콜 지원에 이르기까지 풍부한 네트워크 프로그래밍 기능을 제공합니다.

  6. 풍부한 라이브러리: Java는 파일 입출력, 데이터베이스 연동, 네트워킹, GUI 등 많은 분야에 걸친 방대한 라이브러리를 제공합니다.

이러한 특징들은 Java가 다양한 환경에서 널리 사용되는 언어가 되게 만들었습니다. Java는 웹 애플리케이션, 모바일 애플리케이션(특히 안드로이드), 임베디드 시스템, 빅데이터 처리 등 다양한 분야에서 활용되며, 이에 걸맞는 다양한 개발 도구와 프레임워크도 풍부하게 제공되고 있습니다.

Java 버전별 특징에 대해


Java 8, 11, 17은 각각 JDK(Java Development Kit)의 메이저 버전으로, 각 버전은 주요 기능 추가 및 변경 사항을 포함하고 있습니다.


  1. Java 8
    • 람다 표현식 (Lambda Expressions): 함수형 프로그래밍을 가능하게 하는 중요한 기능이며, 코드를 더욱 간결하고 가독성 좋게 작성할 수 있게 해줍니다.
    • 스트림 API (Stream API): 데이터를 보다 효과적으로 처리할 수 있는 기능을 제공합니다. 대량의 데이터를 처리하거나 병렬처리를 필요로 하는 작업에 유용합니다.
    • Optional 클래스: NullPointer 예외를 방지하기 위한 기능을 제공합니다. Null을 직접 다루는 대신 Optional 객체를 사용하여 Null 값을 안전하게 처리할 수 있습니다.
    • 날짜 및 시간 API (Date and Time API): 이전의 복잡하고 문제가 많았던 Date 및 Calendar API를 대체하는 새로운 날짜 및 시간 API를 제공합니다.
    • Java 8의 Default GC는 Parallel GC입니다. Parallel GC는 멀티 쓰레드를 이용해 가비지 컬렉션을 수행하며, 특히 힙 메모리가 크고, CPU 코어 수가 많은 환경에서 잘 동작합니다.
  2. Java 11
    • 실행 가능한 Java 소스 코드 파일 지원: java 명령어를 사용하여 Java 소스 코드 파일을 직접 실행할 수 있게 해줍니다. 이로써, 별도의 컴파일 과정 없이 Java 소스 코드를 실행할 수 있습니다.
    • HTTP 클라이언트 API: HTTP/1.1 및 HTTP/2를 모두 지원하는 새로운 HTTP 클라이언트 API를 제공합니다. 이 API는 동기 및 비동기 프로그래밍 모델을 모두 지원합니다.
    • 삭제된 기능들: JavaFX, Java EE, 그리고 CORBA와 같은 몇몇 기능들이 JDK에서 제거되었습니다. 이 기능들은 이제 자바 플랫폼에서 분리되어 별도로 제공됩니다.
    • Java 11에서도 Default GC는 여전히 Parallel GC입니다. 그러나 이 버전에서는 새로운 ZGC와 Shenandoah GC가 실험적으로 소개되었습니다. 이 두 GC는 모두 짧은 중단 시간을 목표로 하는 로우 대기 시간(Low Latency) GC입니다.
  3. Java 17
    • 패턴 매칭 for switch (Pattern Matching for switch): Java 17에는 switch 표현식에 패턴 매칭을 적용할 수 있는 기능이 추가되었습니다. 이를 통해 코드의 가독성을 향상시키고 오류를 줄일 수 있습니다.
    • Sealed Classes: 클래스나 인터페이스가 어떤 다른 클래스나 인터페이스에 의해 상속 또는 구현될 수 있는지 제한할 수 있는 기능입니다.
    • Spring Boot 3.0은 Java 17 이상에서만 지원합니다.
    • 애플 M1 프로세서 탑재 제품군에 대해 정식 지원합니다.
    • Java 17에서도 기본적으로 Parallel GC가 설정되어 있습니다. 이 외에 G1 GC와 ZGC가 일반 가용성으로 업데이트되었으며, Shenandoah GC가 더욱 안정화되었습니다.

Java 프로그램 실행 과정에 대해


자바 프로그램이 실행되는 과정은 크게 소스 코드의 작성, 컴파일, 클래스 로드, 링크, 초기화, 그리고 실행 단계로 이루어집니다.


  1. 소스 코드 작성: 프로그래머는 자바의 문법을 이용해 .java 확장자를 가진 자바 소스 코드 파일을 작성합니다.
  2. 컴파일: 작성된 소스 코드는 자바 컴파일러(‘javac’)에 의해 바이트코드로 변환되며, 이 결과 .class 확장자를 가진 파일이 생성됩니다. 바이트코드는 플랫폼에 독립적인 코드로서, 모든 환경의 JVM에서 동일하게 실행될 수 있습니다.
  3. 클래스 로딩: 런타임 시, JVM의 클래스 로더는 필요한 클래스 파일들을 로드합니다. 클래스 로더는 파일 시스템이나 네트워크 등에서 바이트코드가 담긴 .class 파일을 찾아 메모리에 적재하는 역할을 합니다.
  4. 링크: 로딩된 클래스들은 링크 과정을 거쳐서 JVM이 사용할 수 있는 상태로 준비됩니다. 이 과정은 검증, 준비, 그리고 (선택적으로) 해석 단계를 포함하며, 클래스나 인터페이스의 바이트코드 검증, 정적 변수의 메모리 할당과 초기화, 그리고 바이트코드의 심볼릭 메모리 참조를 메서드 영역의 직접 참조로 변환하는 작업 등을 수행합니다.
  5. 초기화: 스태틱 변수들이 초기 값으로 대체되고, 스태틱 블록이 실행됩니다. 이 과정은 클래스가 처음으로 활성화 될 때 수행됩니다.
  6. 실행: JVM의 실행 엔진이 바이트코드를 해석하거나 JIT 컴파일러를 이용해 네이티브 코드로 변환한 후 실행합니다.

이런 식으로, 자바 프로그램은 작성부터 실행까지 여러 단계를 거치며, 이 각각의 단계에서 발생할 수 있는 오류나 이슈를 이해하고 대응하는 것이 자바 개발자의 중요한 역량 중 하나입니다.

main 메서드에서 static이 생략된다면


Java에서 main 메서드에 static 키워드가 생략된다면, 프로그램은 실행되지 않습니다.


Java 애플리케이션의 진입점인 main 메서드는 JVM이 클래스 로딩 없이 호출할 수 있어야 합니다. 이를 위해 main 메서드는 static으로 선언되어야 합니다. static 키워드는 메서드가 객체 인스턴스에 속하지 않고 클래스 자체에 속하도록 지정합니다. 따라서 객체를 생성하지 않고도 호출이 가능합니다.

따라서 main 메서드에 static 키워드가 생략된다면, JVM은 main 메서드를 찾을 수 없게 되므로 프로그램은 시작되지 않습니다. 이때는 “Main method not found in class”라는 오류 메시지가 표시됩니다.

정리하자면, Java 프로그램의 실행을 위해서는 main 메서드를 static으로 선언해야 하며, 이를 생략하면 프로그램이 실행되지 않습니다.

컬렉션 프레임워크 주요 인터페이스


List, Set, Map은 Java Collection Framework의 주요 인터페이스로, 데이터를 저장하고 관리하는 데 사용됩니다. List는 순서를 유지하고 중복을 허용하는 구조, Set은 중복을 허용하지 않는 구조, Map은 키와 값의 쌍으로 이루어진 구조로 동작합니다.


  1. List 인터페이스: 순서가 있는 컬렉션으로, 인덱스를 통한 접근이 가능하며, 중복된 요소를 허용합니다.
    • ArrayList: 동적 배열로 구현되어 검색에 빠르고, 크기 변경 시 비효율적일 수 있습니다.
    • LinkedList: 양방향 연결 리스트로 구현되어 삽입과 삭제가 빠르며, 검색은 상대적으로 느립니다.
  2. Set 인터페이스: 중복된 요소를 허용하지 않는 컬렉션으로, 일반적으로 순서를 유지하지 않습니다.
    • HashSet: 해시 테이블을 사용하여 요소를 저장하므로 검색, 삽입, 삭제가 빠르며, 순서가 없습니다.
    • LinkedHashSet: 해시 테이블과 연결 리스트를 함께 사용하여 순서를 유지합니다.
    • TreeSet: 이진 검색 트리로 구현되어 정렬된 순서를 유지합니다.
  3. Map 인터페이스: 키와 값의 쌍으로 이루어진 요소를 저장하는 구조로, 키의 중복을 허용하지 않습니다.
    • HashMap: 키의 해시 값을 사용하여 값을 저장하므로 삽입과 검색이 빠르며, 순서를 유지하지 않습니다.
    • LinkedHashMap: 해시 테이블과 연결 리스트를 사용하여 삽입 순서를 유지합니다.
    • TreeMap: 키의 정렬 순서에 따라 값을 저장하는 레드-블랙 트리로 구현됩니다.
  4. 요약: List는 순서와 중복을 유지, Set은 중복을 제거, Map은 키와 값의 매핑을 관리하는 등 각 인터페이스와 구현 클래스는 특정 목적과 상황에 적합하게 설계되어 있습니다. 따라서 상황과 요구사항에 맞게 적절한 컬렉션 타입과 클래스를 선택하여 사용하는 것이 중요합니다.

List와 Array에 대해


리스트(List): 자바에서 List 인터페이스는 객체들의 순서 있는 컬렉션을 나타내며, 크기가 동적으로 변화합니다. 예를 들어 ArrayList, LinkedList 등의 클래스가 있습니다.
배열(Array): 자바에서 배열은 고정된 크기의 연속된 메모리 공간에 동일한 타입의 요소를 저장하는 데이터 구조입니다. 예를 들어 int[] array = new int[10];과 같이 선언됩니다.


  1. 크기의 유연성:
    • 리스트: 리스트는 크기가 가변적이며, 요소의 추가와 삭제가 자유로워 크기를 미리 알 수 없는 경우에 유용합니다.
    • 배열: 배열은 선언 시점에 크기가 고정되므로, 크기 변경이 필요하면 새 배열을 생성해야 합니다.
  2. 성능과 메모리 효율성:
    • 리스트: ArrayList는 내부적으로 배열을 사용하며 크기 재조정에 따른 오버헤드가 있을 수 있고, LinkedList는 추가/삭제가 빠르나 메모리 사용이 상대적으로 많을 수 있습니다.
    • 배열: 인덱스를 통한 빠른 접근이 가능하며 메모리 효율성이 높습니다. 하지만 크기가 고정되어 있어서 미리 알고 있는 정확한 크기의 데이터 저장에 적합합니다.
  3. 타입 안정성과 API:
    • 리스트: 제네릭을 사용하여 타입 안정성을 제공합니다. 다양한 메서드와 기능을 제공하여 개발의 유연성과 편리성을 높입니다.
    • 배열: 동일한 타입만 저장되므로 타입 안정성이 있습니다. API는 제한적입니다.
  4. 언제 어느 것을 사용하는 것이 효율적인지:
    • 리스트: 데이터의 크기가 동적으로 변하거나 다양한 컬렉션 메서드를 활용해야 하는 경우 사용합니다.
    • 배열: 데이터 크기가 고정되어 있고, 메모리 효율성과 빠른 접근 속도가 중요한 경우 사용합니다.
  5. 요약:
    • 리스트: 동적인 크기 변화와 다양한 메서드 지원으로 사용의 유연성과 편리성을 제공합니다.
    • 배열: 고정된 크기와 메모리 효율성으로 성능에 중점을 둔 사용에 적합합니다.

리스트와 배열은 각각의 사용 사례에 따라 선택되어야 하며, 상황과 요구사항에 맞게 적절한 구조를 선택하는 것이 중요합니다.

JVM의 역할에 대해


JVM(Java Virtual Machine)은 자바 바이트코드(.class 파일)를 OS에 특화된 코드로 변환하여 실행하는 역할을 합니다. 이를 통해 자바는 플랫폼 독립적인 언어가 될 수 있습니다.


  1. 플랫폼 독립성 제공: JVM은 자바 코드가 한번 작성되면 어느 기기에서든 실행될 수 있도록 플랫폼 독립성을 제공합니다. 자바 코드는 JVM이 설치된 어느 플랫폼에서든 실행될 수 있습니다. 이는 자바 코드가 바이트 코드로 컴파일되며, 이 바이트 코드가 JVM 상에서 실행되기 때문입니다. JVM은 이 바이트 코드를 해당 플랫폼의 기계어로 변환하므로, 개발자는 플랫폼에 따라 코드를 다르게 작성할 필요가 없습니다.
  2. 메모리 관리: JVM은 자바 애플리케이션의 메모리 관리를 담당합니다. JVM은 가비지 컬렉션을 통해 동적 메모리 할당 및 해제를 관리하므로, 개발자는 메모리 관리에 대해 걱정할 필요가 없습니다. 이를 통해 메모리 누수와 같은 문제를 방지하며, 개발자가 보다 비즈니스 로직에 집중할 수 있도록 돕습니다.
  3. 보안 제공: JVM은 클래스 로더와 바이트 코드 검증기를 통해 자바 애플리케이션의 보안을 관리합니다. 부적절한 코드나 메모리 접근을 방지하여 안전한 실행 환경을 제공합니다.

결국, JVM의 역할은 자바가 한 번 작성하면 어디서든 실행 가능(Write Once, Run Anywhere) 라는 원칙을 이룰 수 있도록 해줍니다. 이는 개발자가 다양한 플랫폼에서 동일하게 작동하는 애플리케이션을 더 쉽게 개발할 수 있게 해줍니다.

JVM 메모리 구조에 대해


JVM(Java Virtual Machine)의 메모리 구조는 주로 메소드 영역, 힙 영역, 스택 영역, PC 레지스터, 그리고 네이티브 메소드 스택으로 구성되어 있습니다. 각 영역은 JVM이 프로그램을 실행하는 데 필요한 데이터를 저장하고 관리합니다.


  1. 메소드 영역(Method Area): JVM이 읽어 들인 각 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메소드 데이터, 메소드와 생성자의 바이트코드, 그리고 static 변수 등의 데이터를 저장합니다.
  2. 힙 영역(Heap Area): 모든 클래스의 인스턴스와 배열이 이 영역에 할당됩니다. 즉, 우리가 new 키워드를 사용해 생성하는 모든 객체는 이 힙 영역에 생성되며, 가비지 컬렉션의 대상이 됩니다.
  3. 스택 영역(Stack Area): 각 스레드마다 하나씩 존재하며, 메소드의 호출과 과정에서 중간 결과를 저장하고, 메소드가 작업을 수행하는 동안 로컬 변수를 저장하는 등의 역할을 합니다.
  4. PC 레지스터(PC Register): 현재 수행 중인 JVM 명령의 주소를 저장합니다. 이 레지스터 역시 스레드마다 하나씩 생성됩니다.
  5. 네이티브 메소드 스택(Native Method Stack): 네이티브 메소드를 위한 스택입니다. 자바 외의 언어로 작성된 네이티브 코드를 위한 공간입니다.

Java Stream에 대해


자바에서 스트림(Stream)은 데이터를 추상화하여 다루는, 데이터의 흐름을 나타내는 객체입니다.


스트림은 자바 8에서 추가된 기능으로, 복잡한 데이터 처리를 간결하고 효율적으로 할 수 있도록 도와줍니다. 배열이나 컬렉션의 데이터를 함수형 프로그래밍 방식으로 처리할 수 있게 하며, 병렬처리 기능도 지원합니다.

스트림은 ‘원본 데이터 변경 없이’ 데이터를 처리합니다. 이는 중간 처리 과정에서 새로운 스트림을 생성하면서 원본 데이터를 보호합니다. 또한, 스트림은 ‘게으른 연산’을 통해 필요한 시점에만 데이터를 처리하며, 이는 효율성과 성능 향상에 기여합니다.

스트림은 크게 ‘중간 연산’과 ‘최종 연산’으로 나뉩니다. 중간 연산은 filter, map 등의 연산을 포함하며, 이는 스트림을 반환하여 연산의 연속성을 보장합니다. 최종 연산은 collect, forEach, reduce 등이 있으며, 스트림을 닫고 결과를 반환하거나 소비하는 연산입니다.

따라서, 자바의 스트림은 데이터의 흐름을 표현하고, 효율적이고 선언적인 데이터 처리를 가능하게 합니다.

Java Stream은 어떤 상황에서 활용될 수 있는가


자바의 스트림(Stream)은 대용량 데이터 처리, 복잡한 데이터 연산, 병렬처리 등 다양한 상황에서 활용할 수 있습니다.


  1. 대용량 데이터 처리: 스트림은 대용량의 데이터를 효율적으로 처리하는데 유용합니다. 스트림은 ‘게으른 연산’을 수행하기 때문에, 필요한 시점에서만 데이터를 처리합니다. 따라서 대용량 데이터를 처리할 때 메모리 사용량을 최소화하는 데 도움이 됩니다.

  2. 복잡한 데이터 연산: 스트림은 filter, map, reduce 등 다양한 연산을 제공하므로 복잡한 데이터 처리를 쉽게 할 수 있습니다. 이러한 연산들을 사용하면, 데이터 필터링, 변환, 집계 등 복잡한 연산을 선언적으로 수행할 수 있습니다.

  3. 병렬처리: 스트림은 parallelStream() 메소드를 통해 병렬처리를 지원합니다. 이를 통해 멀티 코어 프로세서의 이점을 최대한 활용하여 성능을 향상시킬 수 있습니다. 하지만 주의할 점은, 모든 작업이 병렬 처리에 적합하지 않으며 공유 변경 가능한 데이터 등에 대한 동기화 문제를 고려해야 합니다.

따라서 자바의 스트림은 대용량 데이터 처리, 복잡한 데이터 연산, 병렬처리 등 다양한 상황에서 활용될 수 있습니다. 그러나 스트림의 특성과 사용 시 주의점을 이해하고 적절한 상황에서 사용해야 합니다.

병렬 처리는 언제 적용될 수 있는가


웹 애플리케이션에서 병렬처리를 적용하는 것은 작업의 독립성, 데이터의 불변성, 동기화 오버헤드 등을 고려해야 합니다. 적절하게 관리되지 않은 병렬처리는 동시성 문제를 초래하고 성능 저하를 가져올 수 있습니다.


병렬 처리는 큰 데이터셋을 처리하거나, 많은 사용자 요청을 처리하는 등의 작업에서 효율성을 높이는 데 매우 유용합니다. 예를 들어, 이커머스 시스템에서는 병렬 처리가 다양한 방법으로 활용될 수 있습니다.

  1. 상품 추천: 이커머스 플랫폼에서 사용자에게 개인화된 상품 추천을 제공하는 작업은 대량의 데이터를 처리해야 하므로 병렬 처리가 필요합니다. 사용자의 구매 이력, 검색 이력, 클릭 이력 등의 데이터를 기반으로 각 사용자에게 최적화된 추천을 계산하는 작업을 여러 코어 또는 서버에 분배하여 동시에 수행할 수 있습니다.

  2. 주문 처리: 특정 시간에 주문이 폭증하는 경우(예: 특별 행사, 할인 시즌 등), 이를 모두 처리하기 위해 병렬 처리가 필요합니다. 각 주문을 독립적인 작업으로 간주하고, 여러 서버나 쓰레드에 분배하여 동시에 처리할 수 있습니다.

  3. 상품 검색: 사용자가 특정 상품을 검색하면, 검색 쿼리는 수천, 수만, 수억 개의 상품 데이터 중에서 일치하는 상품을 찾아야 합니다. 이런 대규모 검색 작업은 병렬 처리를 통해 속도를 높일 수 있습니다.

이렇게 병렬 처리는 이커머스 시스템의 성능과 효율성을 크게 향상시킬 수 있는 기술입니다. 하지만 병렬 처리를 설계하고 구현하는 것은 복잡하며, 동시성 제어, 동기화 등의 추가적인 고려사항이 필요합니다.

병렬 처리 조건에 해당하는 독립성이란


병렬 처리는 여러 작업을 동시에 처리하는 것을 말합니다. 이 방식은 작업이 서로 독립적일 때 가장 효과적입니다. ‘작업의 독립성’이라는 개념은 한 작업의 결과가 다른 작업에 영향을 미치지 않는 상태를 가리킵니다.


병렬 처리에서 독립적인 작업이란 한 작업이 다른 작업의 수행에 영향을 주지 않는 작업을 말합니다. 이런 종류의 작업은 다른 작업의 결과나 상태에 대해 알 필요 없이 각각 독립적으로 실행할 수 있습니다.

예를 들어, 여러 사진 파일을 동시에 다운로드 받는 작업을 생각해보겠습니다. 각 사진 파일을 다운로드 받는 작업은 서로 독립적입니다. 즉, 한 사진 파일의 다운로드 상태나 속도가 다른 사진 파일의 다운로드에 영향을 주지 않습니다. 따라서 이런 경우에 병렬 처리를 통해 각 사진 파일을 동시에 다운로드 받으면 전체적인 다운로드 시간을 크게 단축시킬 수 있습니다.

반면에, 연산의 결과가 다음 연산의 입력으로 사용되는 경우, 이 작업들은 서로 의존성을 가지게 됩니다. 예를 들어, 피보나치 수열을 계산하는 경우, 각 숫자는 이전 두 숫자의 합으로 계산됩니다. 이러한 작업은 독립적으로 수행될 수 없으므로, 병렬 처리를 적용하더라도 성능 향상을 기대하기 어렵습니다.

따라서 병렬 처리를 적용할 때는 작업의 독립성을 먼저 고려해야 하며, 작업이 서로 의존성을 가지는지 확인하는 것이 중요합니다. 독립적인 작업들에 대해서만 병렬 처리를 적용하면, 리소스를 효율적으로 활용하여 전체 작업의 수행 시간을 줄일 수 있습니다.

OOP 특징에 대해


객체 지향 프로그래밍(OOP, Object-Oriented Programming)의 주요 특징은 캡슐화, 상속, 다형성, 그리고 추상화입니다.


  1. 캡슐화, Encapsulation: 캡슐화는 객체의 데이터와 메소드를 하나로 묶는 프로세스입니다. 이를 통해 객체의 내부 상태를 외부에서 직접 변경할 수 없게 하며, 오직 객체의 메소드를 통해서만 데이터를 변경하거나 검색할 수 있습니다. 캡슐화는 데이터 효율성과 보안성을 높이는 데 기여합니다.

  2. 상속, Inheritance: 상속은 클래스 간에 코드를 재사용하도록 하는 기능입니다. 하나의 클래스(부모 클래스 또는 슈퍼 클래스라고도 함)에서 정의된 특성이 다른 클래스(자식 클래스 또는 서브 클래스라고도 함)에게 전달됩니다. 이를 통해 코드의 중복성을 줄이고 가독성을 향상시킬 수 있습니다.

  3. 다형성, Polymorphism: 다형성은 동일한 메소드나 연산자를 사용하여 다양한 방식으로 동작하게 할 수 있는 기능입니다. 이를 통해 메소드 오버로딩이나 오버라이딩을 사용하여 여러 클래스나 객체에게 동일한 인터페이스를 제공할 수 있습니다.

  4. 추상화, Abstraction: 추상화는 복잡한 시스템을 간단한 개념으로 나타내는 기능입니다. 이를 통해 사용자는 시스템의 복잡한 내부 동작 방식을 몰라도 시스템을 사용할 수 있습니다. 추상화는 코드의 복잡성을 줄이고 사용자 친화적인 인터페이스를 제공하는데 중요합니다.

이 4가지 특징은 객체 지향 프로그래밍이 추구하는 가장 중요한 원칙 중 일부이며, 이들을 통해 개발자는 더 효과적이고 효율적인 프로그램을 작성할 수 있습니다.

SOLID 원칙에 대해


SOLID 원칙은 객체 지향 프로그래밍과 설계에 대한 다섯 가지 핵심 개념을 나타냅니다. SOLID 원칙은 유연하고, 유지보수가 가능하며, 이해하기 쉬운 시스템을 만드는 데 도움이 되는 가이드라인을 제공합니다. 이 원칙들은 서로 결합되어 동작하며, 하나의 원칙을 준수하면 다른 원칙도 자연스럽게 준수하게 되는 경우가 많습니다.


  1. 단일책임 원칙, Single Responsibility Principle (SRP): 이 원칙은 “하나의 클래스는 하나의 책임만을 가져야 한다“는 원칙입니다. 즉, 클래스를 변경해야 하는 이유는 단 하나여야 합니다.

  2. 개방-폐쇄 원칙, Open-Closed Principle (OCP): 이 원칙은 “소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려있고, 수정에는 닫혀있어야 한다”는 원칙입니다. 즉, 기존의 코드를 변경하지 않으면서 기능을 추가할 수 있어야 합니다.

  3. 리스코프 치환 원칙, Liskov Substitution Principle (LSP): 이 원칙은 “하위 클래스가 자신의 상위 클래스를 언제나 대체할 수 있어야 한다“는 원칙입니다. 이는 상속의 본질을 나타내며, 자식 클래스는 부모 클래스의 책임을 무시하거나 재정의하지 않고 확장만 수행해야 함을 의미합니다.

  4. 인터페이스 분리 원칙, Interface Segregation Principle (ISP): 이 원칙은 “클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다”는 원칙입니다. 큰 덩어리의 인터페이스보다는, 구체적인 동작을 명시한 여러 개의 인터페이스가 낫다는 것이 이 원칙의 핵심입니다.

  5. 의존 역전 원칙, Dependency Inversion Principle (DIP): 이 원칙은 “추상화된 모듈에 의존해야지, 구체화된 모듈에 의존하는 것은 피해야 한다.“는 원칙입니다. 이는 직접적인 의존 관계를 느슨하게 만들어 코드의 유연성을 높이는데 도움이 됩니다.

SOLID 원칙 중 리스코프 치환 원칙에 대해


리스코프 치환 원칙(Liskov Substitution Principle, LSP)은 객체 지향 프로그래밍에서의 설계 원칙 중 하나로, 하위 클래스는 언제든지 상위 클래스를 대체할 수 있어야 한다는 원칙입니다.


Rectangle (사각형) 이라는 클래스가 있다고 가정해봅시다. Rectangle 클래스에는 width (너비) 와 height (높이) 라는 속성이 있고, setHeightsetWidth 와 같은 메서드를 통해 이들 속성을 변경할 수 있습니다.

이제 Square (정사각형) 라는 하위 클래스를 만들고자 합니다. Square 클래스는 Rectangle 클래스를 상속 받을 수 있지만, 정사각형의 특성상 너비와 높이는 항상 같아야 합니다. 이 경우, setWidth 또는 setHeight 메서드를 호출할 때마다 두 속성을 동시에 변경해야 합니다.

하지만 이런 방식으로 구현하면, Square 클래스는 Rectangle 클래스를 완벽하게 대체할 수 없습니다. 예를 들어, Rectangle 클래스의 인스턴스를 받아 너비와 높이를 각각 변경하는 함수가 있다면, 이 함수에 Square 클래스의 인스턴스를 넣으면 의도하지 않은 결과를 얻게 될 것입니다. 따라서 이런 구조는 리스코프 치환 원칙을 위반하게 됩니다.

이를 해결하는 한 가지 방법은 Square 클래스가 Rectangle 클래스를 상속받지 않고, Shape 라는 더 일반적인 상위 클래스를 상속받게 하는 것입니다. 이렇게 하면 각 클래스에서 해당하는 속성을 각각 관리할 수 있으며, 리스코프 치환 원칙을 만족시킬 수 있습니다.

이처럼 리스코프 치환 원칙은 상속 구조를 설계할 때 매우 중요한 원칙이며, 이를 통해 설계의 유연성과 확장성을 높일 수 있습니다.

SOLID 원칙 중 개방-폐쇄 원칙에 대해


개방-폐쇄 원칙이란, 소프트웨어의 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만, 수정에 대해서는 닫혀 있어야 한다는 원칙입니다. 즉, 기존의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있어야 합니다.


예시로 할인 정책을 정의하는 DiscountPolicy라는 인터페이스를 정의하고, 이를 구현하는 FixDiscountPolicyRateDiscountPolicy라는 클래스를 만들었습니다. 이렇게 함으로써, 새로운 할인 정책이 필요하면 DiscountPolicy 인터페이스를 구현하는 새로운 클래스를 만들어 주면 되고, 기존의 OrderServiceImpl 코드(클라이언트)는 전혀 변경하지 않아도 됩니다.

OrderServiceImpl(클라이언트)은 구체적인 할인 정책이 어떤 것인지 알 필요 없이 DiscountPolicy 인터페이스의 discount 메서드만 호출하면 됩니다. 즉, OrderServiceImplDiscountPolicy에 대해서는 닫혀 있고, 새로운 할인 정책 클래스에 대해서는 열려 있습니다.

이렇게 인터페이스와 구현을 분리하여 개방-폐쇄 원칙을 지키는 설계는 유연성, 재사용성, 유지보수성 등의 객체지향 프로그래밍의 장점을 제대로 활용할 수 있게 합니다.

애자일에 대해


애자일(Agile)은 프로젝트 관리와 소프트웨어 개발에 있어서의 접근법으로, 일정한 주기를 가진 프로젝트의 여러 단계를 반복적으로 진행함으로써 더 빠른 응답성과 유연성을 가능하게 하는 방법론입니다.


애자일은 빠르게 변하는 요구사항에 대응하기 위해 만들어진 소프트웨어 개발 방법론입니다. 이는 2001년 ‘애자일 소프트웨어 개발 선언’에서 처음 소개되었습니다. 애자일의 주요 목표는 고객에게 가치를 제공하는 데 중점을 두며, 이는 소프트웨어를 빠르고 지속적으로 제공함으로써 달성됩니다.

애자일 방법론은 일련의 반복적인 작업 단계, 일명 ‘스프린트’를 통해 이루어집니다. 각 스프린트는 일반적으로 1~4주 사이의 고정된 기간을 가지며, 각각의 스프린트는 명확한 목표와 결과를 가지고 있습니다. 각 스프린트가 끝날 때마다, 팀은 결과물을 검토하고 이를 통해 무엇이 잘되고 있는지, 무엇이 개선되어야 하는지를 학습합니다. 그런 다음 이러한 피드백을 다음 스프린트의 계획에 반영합니다.

애자일 방법론에는 스크럼(Scrum), 칸반(Kanban), XP(Extreme Programming) 등 여러 하위 방법론이 있습니다. 이러한 모든 방법론은 고객의 요구사항을 빠르게 수용하고 변경 사항을 효율적으로 처리하면서, 팀이 자체적으로 문제를 해결하고 개선하는 능력을 강조합니다.

애자일 방법론은 소프트웨어 개발의 유연성과 효율성을 높여주는 강력한 도구입니다. 그러나 팀이 애자일의 원칙과 가치를 충분히 이해하고 실천하려는 의지가 있어야만 제대로 작동합니다.

클린 아키텍처에 대해


클린 아키텍처(Clean Architecture)는 소프트웨어 설계의 한 가지 접근 방식으로, 비즈니스 로직에 대한 이해를 중심으로 시스템을 설계하는 구조를 말합니다.


클린 아키텍처는 Robert C. Martin(Uncle Bob)이 제시한 소프트웨어 설계 원칙으로, 소프트웨어의 유지보수성, 유연성, 테스트 용이성을 향상시키는 것을 목표로 합니다. 이 아키텍처는 프레임워크, UI, 데이터베이스와 같은 외부 요인과 비즈니스 로직을 분리함으로써, 코어 비즈니스 로직에 집중할 수 있게 해줍니다.

클린 아키텍처는 크게 4개의 계층으로 이루어져 있습니다:

  1. 엔터프라이즈 비즈니스 규칙 (Enterprise Business Rules)
  2. 애플리케이션 비즈니스 규칙 (Application Business Rules)
  3. 인터페이스 어댑터 (Interface Adapters)
  4. 프레임워크와 드라이버 (Frameworks and Drivers) 가장 중심에는 도메인 객체 (비즈니스 로직)가 위치하며, 외부로 갈수록 프레임워크나 UI, 데이터베이스와 같은 기술적인 요소가 위치합니다.

클린 아키텍처를 사용하면 다음과 같은 이점을 얻을 수 있습니다:

  1. 테스트 용이성: 의존성이 분리되어 있어 단위 테스트가 간편하며, 이는 빠른 피드백을 제공해줍니다.
  2. 유지보수성: 한 부분의 변경이 다른 부분에 미치는 영향이 최소화되어 유지보수가 쉬워집니다.
  3. 의존성 역전: 상위 모듈은 하위 모듈에 의존하지 않고, 모두가 추상화에 의존하므로 소스 코드의 흐름을 쉽게 이해할 수 있습니다.
  4. 프레임워크 독립성: 클린 아키텍처는 특정 프레임워크에 의존하지 않으므로, 필요에 따라 다른 프레임워크로 쉽게 변경할 수 있습니다.

도메인 주도 설계에 대해


도메인 주도 설계(Domain-Driven Design, DDD)는 복잡한 소프트웨어를 설계할 때, 비즈니스 도메인 자체를 중심으로 모델링과 설계를 진행하는 접근법입니다.


DDD는 소프트웨어의 주요 복잡성을 처리하는 것이 목표입니다. 이를 위해, DDD는 비즈니스의 핵심 개념과 로직을 반영하는 ‘도메인 모델’을 만드는 것에 중점을 둡니다. 이 모델은 객체 지향적인 접근법을 사용하여 개발되며, 비즈니스의 주요 엔티티, 값, 서비스, 규칙 및 이들 사이의 관계를 정의합니다.

DDD는 다음과 같은 핵심 구성 요소로 구성됩니다:

  1. Entities: 유일한 식별자를 가지고 상태와 행동이 지속되는 도메인 객체.
  2. Value Objects: 식별자가 없지만 불변성을 가지는 도메인 객체.
  3. Aggregates: 엔티티와 값 객체의 그룹으로, 일관성 경계를 형성합니다.
  4. Repositories: 애그리거트의 저장소와 검색을 관리합니다.
  5. Domain Events: 도메인의 상태 변화를 나타내는 이벤트. Services: 도메인 로직을 캡슐화하며, 주로 도메인 모델이 자연스럽게 책임을 수행할 수 없는 경우 사용됩니다. 또한 DDD는 Ubiquitous Language라는 중요한 개념을 도입했습니다. 이는 비즈니스 전문가와 개발자 모두가 사용하는 언어로, 모든 스테이크홀더가 도메인에 대해 동일한 이해를 갖고 대화를 나눌 수 있게 합니다.

DDD의 이점 중 하나는 비즈니스 요구 사항의 변화에 더 잘 대응할 수 있는 유연한 설계를 가능하게 하는 것입니다. 또한, 비즈니스 로직을 중심으로 시스템을 구성함으로써 코드의 이해와 유지보수가 용이해집니다. 하지만 DDD는 복잡한 설계 패턴과 개념을 사용하므로, 단순한 애플리케이션에는 과도한 복잡성을 가져올 수 있습니다. 따라서 DDD는 복잡한 비즈니스 로직이나 비즈니스 규칙이 많은 대형 시스템에 가장 적합합니다.

헥사고날 아키텍처에 대해


헥사고날 아키텍처(Hexagonal Architecture), 또는 포트와 어댑터 아키텍처(Ports and Adapters Architecture)는 애플리케이션의 코어 비즈니스 로직을 외부의 변경 가능한 요소들로부터 격리시키는 아키텍처 패턴입니다.


헥사고날 아키텍처는 앨리스테어 콕번(Alistair Cockburn)에 의해 제안되었으며, 이 패턴의 주요 목표는 애플리케이션의 핵심 로직과 외부 세상을 연결하는 부분을 분리함으로써 애플리케이션 내부의 변경에 대해 외부 세상이 영향 받지 않도록 하는 것입니다. 반대로 외부 세상의 변화가 애플리케이션의 핵심 로직에 영향을 미치지 않게 하여, 유지보수와 테스트, 이식성을 향상시키려는 것입니다.

헥사고날 아키텍처에서 ‘헥사고날’이라는 용어는 애플리케이션의 모든 입/출력이 내부로부터 외부로, 외부로부터 내부로 연결되는 여러 포트를 시각적으로 나타내기 위해 사용됩니다. 이러한 포트는 사용자 인터페이스, 데이터베이스, 웹 서비스 등 다양한 요소에 연결될 수 있으며, 이러한 연결은 ‘어댑터’를 통해 수행됩니다.

이 패턴의 주요 장점은 애플리케이션의 코어 비즈니스 로직이 외부 세상의 변화에 대해 견고하게 유지될 수 있다는 것입니다. 이는 코드의 재사용성을 향상시키며, 시스템의 각 부분을 독립적으로 테스트하고 진화시킬 수 있는 유연성을 제공합니다. 다양한 인프라스트럭처와 프레임워크, 환경 등에 쉽게 적용할 수 있도록 해줍니다.

동기처리와 비동기처리에 대해


동기와 비동기는 소프트웨어 프로그래밍에서 작업이 어떻게 실행되고 관리되는지를 설명하는 용어입니다. 동기 방식은 작업이 순차적으로 실행되며, 한 작업이 완료될 때까지 다음 작업은 대기 상태입니다. 반면, 비동기 방식은 작업이 병렬적으로 실행되며, 한 작업이 완료될 때까지 다른 작업이 대기하지 않고 즉시 실행될 수 있습니다.


동기(synchronous): 동기 방식에서, 함수 또는 작업은 호출한 후 결과를 반환하기까지 프로그램이나 스레드가 대기하는 방식입니다. 이는 프로그램 실행의 단순성과 예측 가능성을 제공하지만, 반면에 한 작업이 오래 걸릴 경우 프로그램 전체가 차단될 수 있어 효율성이 떨어질 수 있습니다. 동기 방식은 순차적인 작업 흐름이 필요한 경우에 이상적입니다.

비동기(asynchronous): 비동기 방식에서, 함수 또는 작업은 호출과 거의 동시에 반환되며, 작업의 완료를 기다리지 않고 다음 작업을 즉시 시작합니다. 작업의 결과는 이후에 콜백, 프로미스, 퓨처, 이벤트 등의 형식으로 전달됩니다. 비동기 방식은 I/O 바운드 작업, 네트워크 호출, 디스크 액세스 등과 같은 블로킹 연산이 많은 경우에 유용합니다. 이 방식은 병렬 처리를 가능하게 하여 성능 향상을 가져올 수 있지만, 동기 방식에 비해 복잡한 프로그래밍 모델을 필요로 합니다.

병렬처리는 어떤 상황에 적용될 수 있는가


병렬 처리는 컴퓨터에서 여러 처리를 동시에 수행하는 계산 방식입니다. 이는 프로세서의 코어나 클러스터, 분산 시스템 등에서 작업을 분배하여 동시에 실행함으로써 전체 작업을 빠르게 완료하는 데에 사용됩니다.


병렬 처리는 큰 데이터셋을 처리하거나, 많은 사용자 요청을 처리하는 등의 작업에서 효율성을 높이는 데 매우 유용합니다. 예를 들어, 이커머스 시스템에서는 병렬 처리가 다양한 방법으로 활용될 수 있습니다.

  1. 상품 추천: 이커머스 플랫폼에서 사용자에게 개인화된 상품 추천을 제공하는 작업은 대량의 데이터를 처리해야 하므로 병렬 처리가 필요합니다. 사용자의 구매 이력, 검색 이력, 클릭 이력 등의 데이터를 기반으로 각 사용자에게 최적화된 추천을 계산하는 작업을 여러 코어 또는 서버에 분배하여 동시에 수행할 수 있습니다.

  2. 주문 처리: 특정 시간에 주문이 폭증하는 경우(예: 특별 행사, 할인 시즌 등), 이를 모두 처리하기 위해 병렬 처리가 필요합니다. 각 주문을 독립적인 작업으로 간주하고, 여러 서버나 쓰레드에 분배하여 동시에 처리할 수 있습니다.

  3. 상품 검색: 사용자가 특정 상품을 검색하면, 검색 쿼리는 수천, 수만, 수억 개의 상품 데이터 중에서 일치하는 상품을 찾아야 합니다. 이런 대규모 검색 작업은 병렬 처리를 통해 속도를 높일 수 있습니다.

이렇게 병렬 처리는 이커머스 시스템의 성능과 효율성을 크게 향상시킬 수 있는 기술입니다. 하지만 병렬 처리를 설계하고 구현하는 것은 복잡하며, 동시성 제어, 동기화 등의 추가적인 고려사항이 필요합니다.