Now Loading ...
-
💾 [CS] 프록시 패턴과 프록시 서버
💾 [CS] 프록시 패턴과 프록시 서버
1️⃣ 프록시 패턴
프록시 패턴(proxy pattern)은 대상 객체(subject)에 접근하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층에 있는 디자인 패턴입니다.
이를 통해 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용합니다.
이는 앞서 설명한 프록시 객체로 쓰이기도 하지만 프록시 서버로도 활용됩니다.
용어: 프록시 서버에서의 캐싱
캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고 캐시 안에 있는 데이터를 활용하는 것을 말합니다.
이를 통해 불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있다는 장점이 있습니다.
2️⃣ 프록시 서버
프록시 서버(proxy server)는 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킵니다.
프록시 서버로 쓰는 nginx
nginx는 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버이며, 주로 Node.js 서버 앞단의 프록시 서버로 활용됩니다.
Node.js의 창시자 라인언 달은 다음과 같이 말했습니다 “Node.js의 버퍼 오버플로우 취약점을 예방하기 위해서는 nginx를 프록시 서버로 앞단에 놓고 Node.js를 뒤쪽에 놓는 것이 좋다.”라고 한 것입니다.
이러한 말은 Node.js 서버를 운영할 때 교과서처럼 참고되어 많은 사람이 이렇게 구축하고 있습니다.
Node.js 서버를 구축할 때 앞단에 nginx를 두는 것이죠.
이를 통해 익명 사용자가 직접적으로 서버에 접근하는 것을 차단하고, 간접적으로 한 단계를 더 거치게 만들어서 보안을 강화할 수 있습니다.
위의 그림처럼 nginx를 프록시 서버로 뒤서 실제 포트를 숨길 수 있고 정적 자원을 gzip 압축하거나, 메인 서버 앞단에서의 로깅을 할 수도 있습니다.
용어: 버퍼 오버플로우
버퍼는 보통 데이터가 저장되는 메모리 공간으로, 메모리 공간을 벗어나는 경우를 말합니다. 이때 사용되지 않아야 할 영역에 데이터가 덮어씌어져 주소, 값을 바꾸는 공격이 발생하기도 합니다.
용어: gzip 압축
LZ77과 Huffman 코딩의 조합인 DEFLATE 알고리즘을 기반으로 한 압축 기술입니다.
gzip 압축을 하면 데이터 전송량을 줄일 수 있지만, 압축을 해제했을 때 서버에서의 CPU 오버 헤드도 생각해서 gzip 압축 사용 유무를 결정해야 합니다.
프록시 서버로 쓰는 CloudFlare
CloudFlare는 전 세계적으로 분산된 서버가 있고 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스입니다.
CloudFlare는 웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰입니다.
또한, 서비스를 배포한 이후에 해외에서 무안가 의심스러운 트래픽이 많이 발생하면 이 떄문에 많은 클라우드 서비스 비용이 발생할 수도 있는데, 이때 CloudFlare가 의심스러운 트래픽인지를 먼저 판단해 CAPTCHA 등을 기반으로 이를 일정부분 막아주는 역할도 수행합니다.
위의 그림처럼 사용자, 크롤러, 공격자가 자신의 웹 사이트에 접속하게 될 텐데, 이때 CloudFlare를 통해 공격자로부터 보호할 수 있습니다.
DDOS 공격 방어
DDOS는 짧은 기간 동안 네트워크에 많은 요청을 보내 네트워크를 마비시켜 웹 사이트의 가용성을 방해하는 사이버 공격 유형입니다.
CloudFlare는 의심스러운 트래픽, 특히 사용자가 접속하는 것이 하닌 시스템을 통해 오는 트래픽을 자동으로 차단해서 DDOS 공격으로부터 보호합니다.
CloudFlare의 거대한 네트워크 용량과 캐싱 전략으로 소규모 DDOS 공격은 쉽게 막아낼 수 있으며 이러한 공격에 대한 방화벽 대시보드도 제공합니다.
HTTPS 구축
서버에서 HTTPS를 구축할 때 인증서를 기반으로 구축할 수도 있습니다.
하지만 CloudFlare를 사용하면 별도의 인증서 설치 없이 좀 더 손쉽게 HTTPS를 구축할 수 있습니다.
용어: CDN(Content Delivery Network)
각 사용자가 인터넷에서 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크를 말합니다.
이를 통해 사용자가 웹 서버로부터 콘텐츠를 다운로드하는 시간을 줄일 수 있습니다.
CORS와 프런트엔트의 프록시 서버
CORS(Cross-Origin Resource Sharing)는 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘입니다.
프런트엔드 개발 시 프런트엔드 서버를 만들어서 백엔드 서버와 통신할 때 주로 CROS 에러를 마주치는데, 이를 해결하기 위해 프런트엔트에서 프록시 서버를 만들기도 합니다.
용어: 오리진
프로토콜과 호스트 이름, 포트의 조합을 말합니다.
예를 들어 https://devkobe24.com:12010/test라는 주소에서 오리진은 https://devkobe24.com:12010을 뜻합니다.
예를 들어 프런트엔드에서는 127.0.0.1:3000으로 테스팅을 하는데 백엔드 서버는 127.0.0.1:12010이라면 포트 번호가 다르기 때문에 CROS 에러가 나타납니다.
이때 프록시 서버를 둬서 프런트엔드 서버에서 요청되는 오리진을 127.0.0.1:12010으로 바꾸는 것입니다.
참고로 127.0.0.1이란 루프백(loopback) IP로, 본인 IP 서버의 IP를 뜻합니다.
localhost나 127.0.0.1을 주소창에 입력하면 DNS를 거치지 않고 바로 본인 PC 서버로 연결됩니다.
위의 그림처럼 프런트엔드 서버 앞단에 프록시 서버를 놓아 /api 요청은 user API, /api2 요청은 user API2에 요청할 수 있습니다.
자연스레 CORS 에러 해결은 물론이며 다양한 API 서버와의 통신도 매끄럽게 할 수 있는 것입니다.
-
💾 [CS] 옵저버 패턴(Observer pattern)
💾 [CS] 옵저버 패턴(Observer pattern).
옵저버 패턴(observer pattern)은 주체가 어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴입니다.
여기서 주체란 객체의 상태 변화를 보고 있는 관찰자이며, 옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 ‘추가 변화 사항’이 생기는 객체들을 의미합니다.
또한, 위의 그림처럼 주체와 객체를 따로 두지 않고 상태가 변경되는 객체를 기반으로 구축하기도 합니다.
옵저버 패턴을 활용한 서비스로는 트위터가 있습니다.
위의 그림처럼 내가 어떤 사람인 주체를 ‘팔로우’ 했다면 주체가 포스팅을 올리게 되면 알림이 ‘팔로워’에게 가야합니다.
또한, 옵저버 패턴은 주로 이벤트 기반 시스템에 사용하며 MVC(Model-View-Controller) 패턴에도 사용됩니다.
예를 들어 주체라고 볼 수 있는 모델(model)에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰에 알려주고 이를 기반으로 컨트롤러(controller) 등이 작동하는 것입니다.
1️⃣ 자바에서의 옵저버 패턴.
// Observer
public interface Observer {
void update();
}
// Subject
public interface Subject {
void register(Observer obj);
void unregister(Observer obj);
void notifyObservers();
Object getUpdate(Observer obj);
}
// Topic
import java.util.ArrayList;
import java.util.List;
public class Topic implements Subject {
private List<Observer> observers;
private String message;
public Topic() {
this.observers = new ArrayList<>();
this.message = "";
}
@Override
public void register(Observer obj) {
if (!observers.contains(obj)) {
observers.add(obj);
}
}
@Override
public void unregister(Observer obj) {
observers.remove(obj);
}
@Override
public void notifyObservers() {
this.observers.forEach(Observer::update);
}
@Override
public Object getUpdate(Observer obj) {
return this.message;
}
public void postMessage(String msg) {
System.out.println("Message sended to Topic: " + msg);
this.message = msg;
notifyObservers();
}
}
// TopicSubscriber
public class TopicSubscriber implements Observer {
private String name;
private Subject topic;
public TopicSubscriber(String name, Subject topic) {
this.name = name;
this.topic = topic;
}
@Override
public void update() {
String msg = (String) topic.getUpdate(this);
System.out.println(name + ":: got message >> " + msg);
}
}
// Main
public class Main {
public static void main(String[] args) {
Topic topic = new Topic();
Observer a = new TopicSubscriber("a", topic);
Observer b = new TopicSubscriber("b", topic);
Observer c = new TopicSubscriber("c", topic);
topic.register(a);
topic.register(b);
topic.register(c);
topic.postMessage("nice to meet you");
}
}
실행 결과
Message sended to Topic: nice to meet you
a:: got message >> nice to meet you
b:: got message >> nice to meet you
c:: got message >> nice to meet you
topic을 기반으로 옵저버 패턴을 구현했습니다.
여기서 topic은 주체이자 객체가 됩니다.
class Topic implements Subject를 통해 Subject interface를 구현했고 Observer a = new TopicSubscriber("a", topic); 으로 옵저버를 선언할 때 해당 이름과 어떠한 토픽의 옵저버가 될 것인지를 정했습니다.
자바: 상속과 구현
위의 코드에 나온 implements 등 자바의 상속과 구현의 특징과 차이에 대해 알아보겠습니다.
상속(extends)
자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있는 것을 말합니다.
이로 인해 재사용성, 중복성의 최소화가 이루어집니다.
구현(Implements)
부모 인터페이스(Interface)를 자식 클래스에서 재정의하여 구현하는 것을 말합니다.
상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야 합니다.
상속과 구현의 차이
상속은 일반 클래스, abstract 클래스를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현합니다.
-
💾 [CS] API(Application Programming Interface)
💾 [CS] API(Application Programming Interface)
API(Application Programming Interface) 는 소프트웨어 간의 상호작용을 가능하게 해주는 인터페이스입니다.
쉽게 말해서 , API는 서로 다른 소프트웨어 시스템이나 애플리케이션이 데이터를 주고 받거나 기능을 사용할 수 있도록 도와주는 규칙과 도구들의 집합입니다.
1️⃣ API의 주요 개념.
1. 인터페이스
API는 소프트웨어 시스템이 다른 시스템이나 애플리케이션과 어떻게 소통할 수 있는지를 정의하는 인터페이스입니다.
이 인터페이스는 어떤 데이터나 기능이 노출되고, 그것들을 어떻게 사용할 수 있는지 규정합니다.
2. 추상화
API는 복잡한 시스템 내부의 구현 세부 사항을 숨기고, 사용자나 개발자가 이해하기 쉽게 필요한 기능만을 제공합니다.
예를 들어, 파일을 열거나, 데이터베이스에 쿼리를 보내거나, 웹 페이지의 데이터를 가져오는 등의 작업을 API를 통해 간단하게 수행할 수 있습니다.
3. 모듈화
API는 특정 기능이나 서비스에 대한 접근을 모듈화합니다.
이렇게 모듈화된 API를 사용하면 개발자가 시스템의 다른 부분에 영향을 주지 않고 독립적으로 기능을 사용하거나 확장할 수 있습니다.
4. 표준화
API는 표준화된 방법으로 기능에 접근할 수 있게 해주기 때문에, 여러 개발자나 시스템이 일관된 방식으로 상호작용할 수 있습니다.
예를 들어, REST API는 웹 기반 애플리케이션에서 데이터를 주고받는 표준 방식입니다.
2️⃣ API의 유형.
1. Web API(웹 API)
웹 서비스나 웹 애플리케이션에서 기능을 제공하는 API입니다.
주로 HTTP를 통해 요청과 응답을 주고받으며, REST, SOAP, GraphQL 등이 그 예입니다.
2. Library API
특정 프로그래밍 언어에서 사용할 수 있는 라이브러리나 프레임워크의 함수와 클래스들에 대한 인터페이스입니다.
예를 들어, Python의 표준 라이브러리에서 제공하는 os 나 sys 모듈도 API의 일종입니다.
3. Operating System API
운영 체제가 제공하는 기능에 접근할 수 있게 해주는 API입니다.
예를 들어, Windows API는 윈도우 애플리케이션이 운영 체제의 기능(파일 관리, UI 구성 요소, 네트워크 등)에 접근할 수 있도록 합니다.
4. Database API
데이터베이스와의 상호작용을 위해 제공되는 API입니다.
JDBC(Java Database Connectivity)는 자바 애플리케이션이 데이터베이스와 상호작용할 수 있도록 돕는 대표적인 데이터베이스 API입니다.
3️⃣ API의 예.
Google Maps API
개발자가 자신의 애플리케이션에 지도 기능을 통합할 수 있도록 Google에서 제공하는 API입니다.
Twitter API
개발자가 트위터의 기능(예: 트윗 가져오기, 트윗 작성)을 자신의 애플리케이션에 통합할 수 있도록 제공되는 API입니다.
Payment Gateway API
PayPal이나 Stripe 같은 결제 서비스에서 제공하는 API로, 애플리게이션에 결제 기능을 통합할 수 있습니다.
4️⃣ API의 중요성.
API는 소프트웨어 개발에서 매우 중요한 역할을 합니다.
그것은 소프트웨어 간의 상호 운용성을 촉진하며, 새로운 애플리케이션을 개발하거나 기존 애플리케이션에 새로운 기능을 추가하는 것을 더 쉽게 만들어 줍니다.
또한, API를 통해 외부 시스템이나 서비스와 통합할 수 있어, 다양한 기능을 제공하는 애플리케이션을 보다 효율적으로 개발할 수 있습니다.
-
💾 [CS] 전략 패턴(Strategy pattern)
💾 [CS] 전략 패턴(Strategy pattern)
1️⃣ 전략 패턴(Strategy pattern)
전략 패턴(Strategy pattern) 은 정책 패턴(Policy pattern) 이라고도 하며, 객채의 행위를 바꾸고 싶은 경우 ‘직접’ 수정하지 않고 전략이라고 부르는 ‘캡슐화한 알고리즘’ 을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴입니다.
아래의 예시 코드는 우리가 어떤 것을 살 때 네이버페이, 카카오페이 등 다양한 방법으로 결제하듯이 어떤 아이템을 살 때 LUNACard로 사는 것과 KAKAOCard로 사는 것을 구현한 예제입니다.
결제 방식의 ‘전략’ 만 바꿔서 두 가지 방식으로 결제하는 것을 구현했습니다.
// PaymentStrategy - interface
public interface PaymentStrategy {
void pay(int amount);
}
// KAKAOCardStrategy
public class KAKAOCardStrategy implements PaymentStrategy{
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public KAKAOCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using KAKAOCard.");
}
}
// LUNACardStrategy
public class LUNACardStrategy implements PaymentStrategy {
private String emailId;
private String password;
public LUNACardStrategy(String emailId, String password) {
this.emailId = emailId;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using LUNACard");
}
}
// Item
public class Item {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
}
// ShoppingCart
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
List<Item> items;
public ShoppingCart() {
this.items = new ArrayList<>();
}
public void addItem(Item item) {
this.items.add(item);
}
public void removeItem(Item item) {
this.items.remove(item);
}
public int calculateTotal() {
int sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}
public void pay(PaymentStrategy pamentMethod) {
int amount = calculateTotal();
pamentMethod.pay(amount);
}
}
// Main
import designPattern.strategy.Item;
import designPattern.strategy.KAKAOCardStrategy;
import designPattern.strategy.LUNACardStrategy;
import designPattern.strategy.ShoppingCart;
public class Main {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item A = new Item("A", 100);
Item B = new Item("B", 300);
cart.addItem(A);
cart.addItem(B);
// pay by LUNACard
cart.pay(new LUNACardStrategy("kobe@google.com", "1234"));
// pay by KAKAOCard
cart.pay(new KAKAOCardStrategy("Minseong Kang", "123456789", "123", "12/01"));
}
}
실행 결과
400 paid using LUNACard
400 paid using KAKAOCard.
위 코드는 쇼핑 카드에 아이템을 담아 LUNACard 또는 KAKAOCard 라는 두 개의 전략으로 결제하는 코드입니다.
용어 : 컨텍스트
프로그래밍에서의 컨텍스트는 상황, 맥락, 문맥을 의미하며 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보를 말합니다.
-
💾 [CS] 도메인(Domain)의 의미.
💾 [CS] 도메인(Domain)의 의미.
도메인(Domain) 은 소프트웨어 개발에서 특정 문제 영역 또는 비즈니스 영역을 지칭하는 용어입니다.
도메인은 소프트웨어 시스템이 해결하고자 하는 문제나 제공하는 서비스와 관련된 특정한 지식, 규칙, 절차 등을 포함한 모든 것을 의미합니다.
1️⃣ 도메인(Domain)의 의미.
1. 문제 영역
도메인은 특정 비즈니스나 문제 영역을 나타내며, 이 영역은 소프트웨어가 해결하려고 하는 실제 세계의 문제와 직접적으로 관련됩니다.
예를 들어, 은행 업무, 전자상거래, 병원 관리, 교육 관리 시스템 등 각각의 도메인은 서로 다른 문제와 규칙을 가지고 있습니다.
2. 도메인 지식
도메인에는 해당 문제 영역에 대한 전문 지식이나 규칙이 포함됩니다.
예를 들어, 금융 도메인에서는 이자 계산, 대출 규정, 계좌 관리와 같은 특정 지식이 중요합니다.
이와 같은 도메인 지식을 바탕으로 소프트웨어의 비즈니스 로직이 정의됩니다.
3. 도메인 모델
도메인은 일반적으로 “도메인 모델(Domain Model)”로 표현됩니다.
도메인 모델은 도메인의 개념, 객체, 엔티티, 관계, 규칙 등을 추상화하여 표현한 것입니다.
예를 들어, 은행 도메인 모델에는 고객(Customer), 계좌(Account), 거래(Transaction) 같은 객체가 포함될 수 있습니다.
도메인 모델은 시스템이 해당 도메인의 문제를 어떻게 해결할지를 정의하는데 중요한 역할을 합니다.
4. 도메인 전문가
도메인 전문가(Domain Expert)는 특정 도메인에 대한 깊은 지식을 가진 사람을 의미합니다.
이들은 비즈니스 핵심 요구 사항과 규칙을 정의하며, 개발자와 협력하여 도메인 모델을 설계하는데 중요한 역할을 합니다.
2️⃣ 도메인의 중요성
도메인은 소프트웨어 개발의 초기 단계에서 매우 중요합니다.
시스템이 해결해야 하는 문제를 명확히 정의하고, 비즈니스 요구 사항을 반영한 도메인 모델을 설계하는 것이 시스템의 성공적인 구현에 필수적입니다.
도메인 지식을 제대로 반영하지 못하면, 시스템이 실제 비즈니스 문제를 해결하는 데 실패할 수 있으며, 이는 프로젝트 실패로 이어질 수 있습니다.
따라서 개발자는 도메인 전문가와 긴밀하게 협력하여 도메인을 정확히 이해하고, 이를 코드로 표현하는 것이 중요합니다.
3️⃣ 도메인 주도 설계(Domain-Driven Design, DDD)
도메인과 관련된 중요한 소프트웨어 설계 접근법 중 하나는 도메인 주도 설계(Domain-Driven Design, DDD) 입니다.
DDD는 도메인 모델을 중심으로 소프트웨어를 설계하는 방법론으로, 도메인의 개념과 규칙을 코드에 직접 반영하여 소프트웨어의 복잡성을 관리하고, 도메인의 변화에 쉽게 적응할 수 있도록 돕습니다.
예시
예를 들어, 전자상거래 도메인 을 생각해보면, 이 도메인에는 다음과 같은 요소들이 포함될 수 있습니다.
고객(Customer) : 상품을 구매하는 사람.
상품(Product) : 고객이 구매할 수 있는 아이템.
주문(Order) : 고객이 상품을 구매할 때 생성되는 거래 기록.
결제(Payment) : 주문에 대한 대금 지불.
이러한 요소들과 그들 간의 관계가 도메인을 구성하며, 소프트웨어 시스템은 이러한 도메인의 개념을 바탕으로 비즈니스 로직을 구현하게 됩니다.
4️⃣ 결론
도메인은 소프트웨어가 다루는 문제의 범위와 관련된 개념, 규칙, 객체들을 나타내며, 이를 정확히 이해하고 모델링하는 것이 성공적인 소프트웨어 개발의 핵심입니다.
도메인 이해를 바탕으로 적절한 비즈니스 로직을 구현하는 것이 소프트웨어의 목표를 달성하는 데 매우 중요합니다.
-
💾 [CS] 비즈니스 로직(Business Logic)이란?
💾 [CS] 비즈니스 로직(Business Logic)이란?
1️⃣ 비즈니스 로직(Business Logic).
비즈니스 로직(Business Logic) 은 소프트웨어 시스템 내에서 특정 비즈니스 도메인에 대한 규칙, 계산, 절차 등을 구현한 부분을 의미합니다.
이 로직은 애플리케이션이 실제 비즈니스 요구 사항을 충족하도록 하는 핵심 기능을 담당합니다.
비즈니스 로직(Business Logic) 은 시스템이 처리해야 하는 업무 규칙과 관련된 의사결정을 포함하며, 데이터의 유효성 검사를 하고, 비즈니스 프로세스를 관리하고, 관련된 계산을 수행하는 역할을 합니다.
2️⃣ 비즈니스 로직의 주요 역할
1. 도메인 규칙 관리.
특정 비즈니스 도메인에서 따라야 하는 규칙을 정의하고 관리합니다.
예를 들어, 은행 시스템에서 계좌 이체 시 잔액이 충분해야 한다는 규칙을 비즈니스 로직에서 처리합니다.
2. 유효성 검사.
입력된 데이터나 시스템 내부에서 사용되는 데이터가 비즈니스 규칙에 맞는지 검증합니다.
예를 들어, 사용자가 입력한 주문의 총액이 0보다 큰지, 재고가 충분한지 등을 검사하는 로직이 포함됩니다.
3. 비즈니스 프로세스 구현.
비즈니스 워크플로우를 구현하여, 각 단계에서 수행해야 하는 작업을 정의하고, 순서대로 실행되도록 관리합니다.
예를 들어, 주문 처리 시스템에서 주문 접수, 결제 처리, 배송 준비 등의 단계가 비즈니스 로직에 포함될 수 있습니다.
4. 계산과 처리.
특정 비즈니스 규칙에 따라 데이터를 계산하거나 처리하는 역할을 합니다.
예를 들어, 세금 계산, 할인 적용, 이자 계산 등이 여기에 포함됩니다.
3️⃣ 비즈니스 로직의 위치
비즈니스 로직은 보통 애플리케이션의 Service 계층 에 위치합니다.
이 계층은 데이터를 처리하는 로직과 사용자 인터페이스를 담당하는 로직을 분리하여, 코드의 재사용성을 높이고 유지보수를 용이하게 합니다.
Service 계층 에서는 비즈니스 로직을 구현하며, 필요한 경우 데이터 접근 계층(Repository) 을 호출하여 데이터를 조회하거나 저장하고, 최종적으로 처리된 결과를 프레젠테이션 계층(Controller) 에 전달합니다.
4️⃣ 비즈니스 로직과 다른 로직의 구분
비즈니스 로직
실제 비즈니스와 관련된 모든 규칙과 프로세스를 정의합니다.
이는 특정 도메인 지식에 기반하며, 도메인 전문가가 주로 요구 사항을 정의합니다.
프레젠테이션 로직
사용자 인터페이스와 관련된 로직으로, 사용자에게 데이터를 표시하거나 입력을 받는 것과 관련됩니다.
데이터 접근 로직
데이터베이스와 상호작용하며, 데이터를 저장하거나 조회하는 작업을 담당합니다.
5️⃣ 비즈니스 로직의 중요성
비즈니스 로직은 애플리케이션의 핵심적인 부분이므로, 이 로직의 정확성은 시스템 전체의 신뢰성과 직결됩니다.
잘 설계된 비즈니스 로직은 애플리케이션이 요구된 비즈니스 목표를 정확히 달성할 수 있도록 돕고, 변경이 필요할 때도 쉽게 확장하거나 수정할 수 있도록합니다.
따라서 비즈니스 로직을 구현할 때는 도메인 전문가와 긴밀하게 협력하여 요구사항을 명확히 이해하고, 이를 코드로 정확히 표현하는 것이 매우 중요합니다.
-
💾 [CS] 팩토리 패턴(factory pattern)
💾 [CS] 팩토리 패턴(factory pattern).
1️⃣ 팩토리 패턴(factory pattern).
팩토리 패턴(factory pattern)은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴입니다.
상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 갖게 됩니다.
그리고 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있게 되니 유지 보수성이 증가됩니다.
예를 들어 라떼 레시피와 아메리카노 레시피, 우유 레시피라는 구체적인 내용이 들어 있는 하위 클래스가 컨베이어 벨트를 통해 전달되고, 상위 클래스인 바리스타 공장에서 이 레시피들을 토대로 우유 등을 생산하는 생산 공정을 생각하면 됩니다.
2️⃣ 자바의 팩토리 패턴
enum CoffeeType {
LATTE,
ESPRESSO
}
abstract class Coffee {
protected String name;
public String getName() {
return name;
}
}
class Latte extends Coffee {
public Latte() {
name = "latte";
}
}
class Espresso extends Coffee {
public Espresso() {
name = "Espresso";
}
}
class CoffeeFactory {
public static Coffee createCoffee(CoffeeType type) {
switch (type) {
case LATTE:
return new Latte();
case ESPRESSO:
return new Espresso();
default:
throw new IllegalArgumentException("Invalid coffee type: " + type);
}
}
}
public class Main {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
System.out.println(coffee.getName()); // latte
}
}
3️⃣ 코드 설명.
팩토리 패턴(Factory Pattern) 은 객체 생성의 로직을 별도의 클래스나 메서드로 분리하여 관리하는 디자인 패턴입니다.
이는 객체 생성에 관련된 코드를 클라이언트 코드에서 분리하여, 객체 생성의 변화에 대한 유연성을 높이고 코드의 유지보수성을 개선하는 데 도움이 됩니다.
팩토리 패턴 은 크게 팩토리 메서드 패턴 과 추상 팩토리 패턴 으로 구분되며, 위 코드 예시는 팩토리 메스드 패턴 의 전형적인 예입니다.
1. CoffeeType 열거형(Enum)
enum CoffeeType {
LATTE,
ESPRESSO
}
설명 : CoffeeType 은 커피의 종류를 나타내는 열거형(Enum)입니다.
이 열거형은 LATTE 와 ESPRESSO 두 가지 타입의 커피를 정의하고 있습니다.
역할 : 커피의 종류를 코드 내에서 명확하게 구분하고, CoffeeFactory 에서 커피 객체를 생성할 때 사용됩니다.
2. Coffee 추상 클래스.
abstract class Coffee {
protected String name;
public String getName() {
return name;
}
}
설명 : Coffee 는 커피 객체의 공통된 속성과 메서드를 정의한 추상 클래스입니다.
name 필드는 커피의 이름을 저장하며, getName() 메서드는 커피의 이름을 반환합니다.
역할 : 구체적인 커피 클래스들이 상속받아야 하는 공통적인 기능을 정의합니다.
3. Latte 와 Espresso 클래스.
class Latte extends Coffee {
public Latte() {
name = "latte";
}
}
class Espresso extends Coffee {
public Espresso() {
name = "Espresso";
}
}
설명 : Latte 와 Espresso 는 Coffee 클래스를 상속받아 구체적인 커피 타입을 구현한 클래스들입니다.
각 클래스는 생성자에서 name 필드를 특정 커피 이름으로 초기화합니다.
역할 : 특정 커피 타입의 객체를 생성하는 역할을 합니다.
4. CoffeeFactory 클래스.
class CoffeeFactory {
public static Coffee createCoffee(CoffeeType type) {
switch (type) {
case LATTE:
return new Latte();
case ESPRESSO:
return new Espresso();
default:
throw new IllegalArgumentException("Invalid coffee tyep: " + type);
}
}
}
설명 : CoffeeFactory 클래스는 팩토리 패턴의 핵심으로, createCoffee() 메서드를 통해 특정 타입의 커피 객체를 생성하여 반환합니다.
CoffeeType 열거형에 따라 적절한 커피 객체를 생성합니다.
역할 : 객체 생성의 로직을 중앙 집중화하여 클라이언트 코드에서 객체 생성의 책임을 분리합니다.
클라이언트는 CoffeeFactory 의 createCoffee() 메서드를 호출하여 원하는 커피 객체를 생성할 수 있습니다.
5. Main 클래스.
public class Main {
public static void main(String[] args) {
Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
System.out.println(coffee.getName()); // latte
}
}
설명 : Main 클래스는 클라이언트 코드로, CoffeeFactory 를 사용하여 LATTE 타입의 커피 객체를 생성하고, 그 이름을 출력합니다.
역할 : 팩토리 패턴을 사용하는 클라이언트 코드로, 직접적으로 객체를 생성하지 않고 팩토리를 통해 객체를 생성합니다.
4️⃣ 팩토리 패턴의 장점.
1. 코드의 유연성 증가.
객체 생성 로직이 중앙화되어 있으므로, 새로운 커피 타입을 추가할 때 클라이언트 코드를 수정할 필요 없이 팩토리 클래스만 수정하면 됩니다.
2. 유지보수성 향상.
객체 생성 코드가 한 곳에 모여 있어 코드의 유지보수가 쉬워집니다.
객체 생성 과정에서의 변경이 필요한 경우에도 팩토리 클래스만 수정하면 됩니다.
3. 코드의 결합도 감소.
클라이언트 코드는 구체적인 클래스에 의존하지 않고, 인터페이스나 추상 클래스를 통해 객체를 다루기 때문에 결합도가 낮아집니다.
5️⃣ 팩토리 패턴의 단점.
1. 클래스의 복잡성 증가.
객체 생성을 위한 팩토리 클래스가 추가됨으로써 클래스의 수가 증가하고, 코드 구조가 다소 복잡해질 수 있습니다.
2. 확장 시 주의 필요.
새로운 커피 타입을 추가할 때마다 팩토리 클래스의 switch 문이나 if-else 문이 증가할 수 있어, 확장성이 제한될 수 있습니다.
이 문제를 해결하기 위해서는 추상 팩토리 패턴이나 다른 디자인 패턴과 결합하는 방법을 고려할 수 있습니다.
6️⃣ 결론.
팩토리 패턴은 객체 생성의 책임을 분리하여 코드의 유연성과 유지보수성을 높이는 강력한 디자인 패턴입니다.
위 코드 예시에서는 커피 객체를 생성하는 로직을 CoffeeFactory 클래스에 모아두어, 클라이언트 코드가 특정 커피 클래스에 직접적으로 의존하지 않도록 하였습니다.
이를 통해 클라이언트 코드는 커피 객체의 생성 방식에 대해 신경 쓰지 않고도 다양한 타입의 커피를 생성하고 사용할 수 있게 됩니다.
-
💾 [CS] 추상화(Abstraction)
💾 [CS] 추상화(Abstraction).
1️⃣ 추상화(Abstraction).
추상화(Abstraction) 는 객체 지향 프로그래밍(Object-Oriented-Programming, OOP)의 중요한 개념 중 하나로, 복잡한 시스템에서 핵심적인 개념이나 기능만을 추려내어 단순화하는 과정입니다.
이를 통해 불필요한 세부 사항을 감추고, 중요한 속성이나 행위만을 노출하여 시스템을 보다 간단하게 이해하고 사용할 수 있게 합니다.
1️⃣ 추상화의 핵심 개념.
1. 본질적인 것만 노출.
시스템의 복잡한 내부 구현을 숨기고, 외부에서는 중요한 기능이나 속성만을 사용할 수 있도록 설계합니다.
예를 들어, 자동차를 운전할 때 운전자는 엔진의 작동 원리나 내부 구조를 몰라도, 운전대, 가속 페달, 브레이크 등의 중요한 인터페이스를 통해 자동차를 조작할 수 있습니다.
2. 복잡성 감소.
추상화를 통해 사용자에게 복잡한 시스템을 단순하게 보이도록 하여, 사용자가 시스템을 쉽게 이해하고 사용할 수 있게 합니다.
이는 특히 큰 시스템이나 라이브러리를 설계할 때 중요합니다.
3. 재사용성과 유지보수성 향상.
추상화를 사용하면, 코드의 재사용성을 높이고 유지보수성을 향상시킬 수 있습니다.
동일한 추상 인터페이스를 구현하는 여러 클래스가 있을 때, 구체적인 클래스 구현을 신경 쓰지 않고 인터페이스를 통해 일관된 방식으로 코드를 사용할 수 있습니다.
2️⃣ 추상화의 예
추상화는 주로 추상 클래스와 인터페이스 를 통해 구현됩니다.
추상 클래스.
추상 클래스는 하나 이상의 추상 메서드를 초함하는 클래스입니다.
추상 메서드는 선언만 되어 있고, 구체적인 구현은 해당 클래스를 상속받는 하위 클래스에서 제공해야 합니다.
abstract class Animal {
abstract void sound(); // 추상 메서드
void breathe() { // 구체적인 메서드
System.out.println("Breathing");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow");
}
}
이 예에서 Animal 클래스는 추상 클래스이고, sound() 메서드는 추상 메서드입니다.
Dog 와 Cat 클래스는 sound() 메서드를 구체적으로 구현합니다.
Animal 클래스는 동물의 일반적인 특징인 breath() 메서드를 포함하지만, sound() 는 동물마다 다르므로 하위 클래스에서 구체화됩니다.
인터페이스
인터페이스는 추상화의 또 다른 형태로, 클래스가 구현해야 하는 메서드의 선언을 포함합니다.
인터페이스 자체는 구현을 가지지 않으며, 구현은 이를 구현하는 클래스에서 제공됩니다.
interface Flyable {
void fly(); // 추상 메서드
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying");
}
}
class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("Airplane is flying");
}
}
여기서 Flyable 인터페이스는 fly() 라는 추상 메서드를 선언하고 있으며, Bird 와 Airplane 클래스는 각각 이 메서드를 구현합니다.
Flyable 인터페이스를 통해, 비행할 수 있는 객체들은 동일한 방식으로 취급될 수 있습니다.
3️⃣ 추상화의 장점.
1. 코드의 간결성.
중요한 부분만 남기고 복잡한 구현 세부 사항을 숨겨, 코드를 간결하고 이해하기 쉽게 만듭니다.
2. 유연한 설계.
구체적인 구현에 의존하지 않기 때문에, 다양한 구현체를 쉽게 교체하거나 확장할 수 있습니다.
3. 재사용성 증가.
추상 클래스나 인터페이스를 통해 여러 클래스에서 공통적으로 사용될 수 있는 구조를 만들 수 있습니다.
4️⃣ 요약.
추상화는 복잡한 시스템에서 불필요한 세부 사항을 감추고 중요한 부분만을 노출하여 시스템을 간단하게 만드는 개념입니다.
이를 통해 코드의 복잡성을 줄이고, 유연성과 재사용성을 높이며, 유지보수를 용이하게 할 수 있습니다.
추상화는 주로 추상 클래스와 인터페이스를 통해 구현됩니다.
-
💾 [CS] 의존성 주입(DI, Dependency Injection)
💾 [CS] 의존성 주입(DI, Dependency Injection).
1️⃣ 의존성 주입(DI, Dependency Injection)
싱글톤 패턴과 같이 사용하기 쉽고 굉장히 실용적이지만 모듈 간의 결합을 강하게 만들 수 있는 단점이 있는 패턴의 경우 의존성 주입(DI, Dependency Injection)을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있습니다.
의존성이란 종속성이라고도 하며 A가 B에 의존성이 있다는 것은 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미합니다.
앞의 그림처럼 메인 모듈(main module)이 ‘직접’ 다른 하위 모듈에 대한 의존성을 주기보다는 중간에 의존성 주입자(dependency injector)가 이 부분을 가로채 메인 모듈이 "간접적" 으로 의존성을 주입하는 방식입니다.
이를 통해 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성이 떨어지게 됩니다.
참고로 이를 ‘디커플링이 된다’ 고도 합니다.
1️⃣ 의존성 주입의 장점
모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션하기도 수월합니다.
또한, 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해집니다.
2️⃣ 의존성 주입의 단점
모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 페널티가 생기기도 합니다.
3️⃣ 의존성 주입 원칙
의존성 주입은 "상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 합니다. 또한, 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 합니다." 라는 의존성 주입 원칙을 지켜주면서 만들어야 합니다.
위 문장에서 “추상화”의 의미.
문장에서 “추상화”는 구체적인 구현에 의존하지 않고, 일반화된 인터페이스나 추상 클래스 등에 의존해야 한다는 것을 뜻합니다.
이 원칙은 의존성 역전 원칙(DIP, Dependency Inversion Principle) 과 관련이 있습니다.
상위 모듈 : 애플리케이션의 상위 계층에서 동작하는 코드, 즉 더 높은 수준의 정책이나 로직을 구현하는 모듈입니다.
하위 모듈 : 상위 모듈에서 호출하거나 사용하는 구체적인 기능이나 세부 사항을 구현하는 코드입니다.
1️⃣ 추상화.
“추상화” 는 객채 지향 프로그래밍(OOP)애서 중요한 개념 중 하나로, 구체적인 구현(details)을 감추고, 더 높은 수준의 개념을 정의하는 것을 의미합니다.
추상화는 구체적인 것보다는 더 일반적이고 보편적인 개념을 다루며, 특정한 구현 사항에 의존하지 않고 인터페이스나 추상 클래스 등을 통해 기능을 정의합니다.
2️⃣ 의존성 주입과 추상화의 관계.
의존성 주입(DI, Dependency Injection)은 의존성 역전 원칙(DIP, Dependency Inversion Principle)을 구현하기 위한 방법 중 하나입니다.
의존성 주입을 사용하면, 상위 모듈이 하위 모듈의 구체적인 구현에 의존하지 않고, 하위 모듈이 구현한 추상화(인터페이스나 추상 클래스)에 의존하도록 코드를 설계할 수 있습니다.
즉, 상위 모듈과 하위 모듈 모두 추상화된 인터페이스에 의존하게 하여, 구체적인 구현이 변경되더라도 상위 모듈의 코드가 영향을 받지 않도록 합니다.
3️⃣ 예시.
아래는 추상화와 의존성 주입을 적용한 예시입니다.
// 추상화된 인터페이스 (추상화)
public interface PaymentProcessor {
void processPayment(double amount);
}
// 하위 모듈 - 구체적인 구현
public class PayPalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
// PayPal을 통해 결제 처리
}
}
public class CreditCardProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
// 신용카드를 통해 결제 처리
}
}
// 상위 모듈 - 추상화에 의존함
public class PaymentService {
private PaymentProcessor paymentProcessor;
// 의존성 주입을 통해 구현체를 주입 받음
public PaymentService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
public void makePayment(double amount) {
paymentProcessor.processPayment(amount);
}
}
위 코드에서 "PaymentService" 는 "PaymentProcessor" 라는 추상화에 의존합니다.
"PaymentService" 는 "PayPalProcessor" 나 "CreditCardProcessor" 의 구체적인 구현을 알 필요가 없으며, 단지 "PaymentProcessor" 인터페이스에 정의된 메서드를 호출합니다.
- 이를 통해 결제 처리 방식이 PayPal에서 신용카드로 변경되더라도 "PaymentService" 는 수정할 필요가 없습나다.
이처럼 “추상화”는 상위 모듈과 하위 모듈이 특정 구현이 아닌, 일반적인 개념에 의존하도록 만들어줌으로써, 코드의 유연성과 재사용성을 높여주는 중요한 개념입니다.
-
💾 [CS] 싱글톤 패턴
💾 [CS] 싱글톤 패턴.
1️⃣ 싱글톤 패턴(Singleton pattern)
싱글톤 패턴(singleton pattern)은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴입니다.
하나의 클래스를 기반으로 여러 개의 개별적인 인스턴스를 만들 수 있지만, 그렇게 하지 않고 하나의 클래스를 기반으로 단 하나의 인스턴스를 만들어 이를 기반으로 로직을 만드는데 쓰입니다.
보통 데이터베이스 연결 모듈에 많이 사용합니다.
하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있습니다.
하지만 의존성이 높아진다는 단점이 있습니다.
2️⃣ Java에서의 싱글톤 패턴.
Java에서 Singleton 패턴을 구현하는 방법은 여러 가지가 있지만, 가장 일반적으로 사용되는 방법 중 몇 가지를 소개하겠습니다.
Eager Initialization(즉시 초기화)
Lazy Initialization(지연 초기화)
Thread-safe Singleton(스레드 안전 싱글톤)
Synchronized Method
Double-checked Locking
Bill Pugh Singleton(Holder 방식)
1️⃣ Eager Initialization(즉시 초기화)
가장 간단한 방법으로, 클래스가 로드될 때 즉시 Singleton 인스턴스를 생성합니다.
public class Singleton {
// 유일한 인스턴스 생성
private static final Singleton instance = new Singleton();
// private 생성자: 외부에서 인스턴스 생성을 방지
private Singleton() {}
// 인스턴스를 반환하는 메서드
public static Singleton getInstance() {
return instance;
}
}
이 방법은 간단하고 직관적이지만, 클래스가 로드될 때 바로 인스턴스가 생성되기 때문에, 인스턴스가 사용되지 않더라도 메모리를 차지하게 됩니다.
2️⃣ Lazy Initialization(지연 초기화)
인스턴스가 처음으로 필요할 때 생성되도록 합니다.
이 방법은 초기화에 드는 비용이 큰 경우 유리합니다.
```java
public class Singleton {
// 유일한 인스턴스를 저장할 변수 (초기에는 null)
private static Singleton instance;
// private 생성자: 외부에서 인스턴스 생성을 방지
private Singleton() {}
// 인스턴스를 반환하는 메서드 (필요할 때만 생성)
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
```
이 방법은 다중 스레드 환경에서 안전하지 않기 때문에, 추가적인 동기화가 필요합니다.
3️⃣ Thread-safe Singleton(스레드 안전 싱글톤)
다중 스레드 환경에서 안전하게 Lazy Initialization을 구현하려면 동기화를 사용합니다.
1️⃣ Synchronized Method
public class Singleton {
private static Singleton instance;
private Singleton() {}
// synchronized 키워드로 스레드 안전하게 만듦
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
이 방법은 안전하지만, 성능에 약간의 영향을 줄 수 있습니다.
'synchronized' 로 인해 여러 스레드가 동시에 ‘getInstance()‘ 를 호출할 때 병목 현상이 발생할 수 있습니다.
2️⃣ Double-checked Locking
이 방법은 성능과 스레드 안전성을 모두 고려한 최적화된 방식입니다.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
여기서 'volatile' 키워드는 인스턴스 변수가 스레드 간에 올바르게 초기화되도록 보장합니다.
4️⃣ Bill Pugh Singleton(Holder 방식)
이 방법은 Lazy Initialization을 사용하면서도, 성능과 스레드 안전성을 모두 보장합니다.
public class Singleton {
private Singleton() {}
// SingletonHolder가 클래스 로드 시점에 초기화됨
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
이 방법은 내부 정적 클래스가 JVM에 의해 클래스 로드 시 초기화되므로, 가장 권장되는 방식 중 하나입니다.
클래스가 로드될 때 초기화가 이루어지므로, 동기화나 추가적인 코드 없이도 스레드 안전성을 보장할 수 있습니다.
3️⃣ Spring Boot와 MySQL 데이터베이스의 연결 그리고 싱글턴 패턴.
Spring Boot에서 MySQL 데이터베이스를 연결할 때, 내부적으로 ‘싱글턴 패턴’ 이 사용됩니다.
그러나 이 패턴을 직접 구현할 필요는 없습니다.
‘Spring Framework’ 자체가 싱글턴 패턴을 활용하여 데이터베이스 연결 및 관리와 관련된 ‘Bean(객체)’ 을 관리합니다.
1️⃣ Spring Boot와 싱글턴 패턴.
Spring Framework는 기본적으로 각 Bean을 싱글턴 스코프로 관리합니다.
이는 특정 클래스의 인스턴스가 애플리케이션 컨텍스트 내에서 한 번만 생성되어 애플리케이션 전반에서 공유됨을 의미합니다.
2️⃣ 데이터베이스 연결에서의 싱글턴 패턴 사용.
DataSource Bean.
Spring Boot에서 MySQL과 같은 데이터베이스에 연결할 때 'DataSource' 라는 'Bean' 을 생성하여 관리합니다.
이 'DataSource' 객체는 데이터베이스 연결을 관리하는 역할을 하며, Spring은 이 'Bean' 을 싱글턴으로 생성하고 관리합니다.
즉, Spring 애플리케이션 내에서는 'DataSource' 객체가 하나만 생성되어 모든 데이터베이스 연결 요청에서 재사용됩니다.
EntityManagerFactory 및 SessionFactory.
JPA나 Hibernate와 같은 ORM을 사용하는 경우, 'EntityManagerFactory' 나 'SessionFactory' 와 같은 객체도 싱글턴 패턴에 의해 관리됩니다.
이들 객체는 데이터베이스 연결을 처리하고 트랜잭션을 관리하며, 역시 Spring에 의해 싱글턴으로 관리됩니다.
Spring의 싱글턴 관리.
Spring은 개발자가 'Bean' 을 직접 싱글턴으로 관리할 필요가 없도록, 애플리케이션의 컨텍스트 내에서 'Bean' 을 싱글턴으로 관리합니다.
데이터베이스와의 연결 관련 클래스들이 이 'Bean' 들로 구성되며, 이는 데이터베이스 연결이 효율적이고 일관되게 관리되도록 보장합니다.
3️⃣ 예시: Spring Boot에서 MySQL 연결 설정.
Spring Boot에서 MySQL 데이터베이스를 연결하기 위한 일반적인 설정은 'application.properties' 파일이나 'application.yml' 파일에 데이터베이스 연결 정보를 추가하는 것입니다.
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
이 설정은 Spring Boot가 'DataSource' 'Bean' 을 자동으로 생성하도록 하며, 이 'Bean' 은 애플리케이션 내에서 싱글턴으로 관리됩니다.
4️⃣ ✏️ 요약
Spring Boot에서 MySQL과 같은 데이터베이스를 연결할 때, Spring은 내부적으로 싱글턴 패턴을 사용하여 데이터베이스 연결을 관리합니다.
'DataSource', 'EntityManagerFactory' 등의 객체가 싱글턴으로 관리되며, 이를 통해 애플리케이션 전반에 걸쳐 일관되고 효율적인 데이터베이스 연결 관리가 이루어집니다.
Spring 자체가 이 패턴을 처리하므로, 개발자는 별도로 싱글턴 패턴을 구현할 필요가 없습니다.
4️⃣ Java Servlet 컨테이너와 MySQL 데이터베이스 연결 그리고 싱글턴 패턴.
Java Servlet 컨테이너에서 MySQL 데이터베이스를 연결할 때, 싱글턴 패턴이 일반적으로 사용됩니다.
다만, 이 패턴은 애플리케이션 코드에서 직접 구현되는 것이 아니라, 서블릿 컨테이너나 데이터베이스 연결 관리 라이브러리에서 사용됩니다.
1️⃣ JDBC DataSource
서블릿 컨테이너(예: Tomcat, Jetty)에서 데이터베이스 연결을 설정할 때 보통 'DataSource' 를 사용합니다.
이 'DataSource' 객체는 보통 싱글턴으로 관리되며, 데이터베이스 연결 풀을 제공합니다.
Connection Pooling
서블릿 컨테이너는 데이터베이스 연결을 관리하기 위해 연결 풀링(Connection pooling)을 사용합니다.
연결 풀은 여러 데이터베이스 연결을 미리 생성하고 재사용하도록 관리합니다.
연결 풀을 관리하는 객채는 'DataSource' 이고, 이는 애플리케이션 내에서 싱글턴으로 관리되어, 여러 서블릿에서 동일한 'DataSource' 객체를 사용하여 효율적으로 데이터베이스에 연결할 수 있습니다.
2️⃣ 싱글턴 패턴의 활용.
DataSource 객체
서블릿 컨테이너는 보통 'DataSource' 객체를 싱글턴으로 관리합니다.
'DataSource' 는 데이터베이스 연결 풀을 관리하며, 이 객체가 한 번만 생성되어 애플리케이션 전반에 걸쳐 재사용됩니다.
Connection 객체
각 요청마다 데이터베이스 연결이 필요할 때마다 새로운 'Connection' 객체가 생성되거나 풀에서 가져오게 됩니다.
하지만 'DataSource' 자체는 싱글턴으로 관리되기 때문에, 동일한 'DataSource' 객체를 통해 연결이 이루어집니다.
3️⃣ 예시: Tomcat에서 DataSource 설정
Tomcat과 같은 서블릿 컨테이너에서 MySQL 데이터베이스와의 연결을 설정하는 일반적인 방법은 'context.xml' 파일에서 'DataSource' 를 정의하는 것입니다.
<Context>
<Resource name="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
maxTotal="100"
maxIdel="30"
maxWaitMillis="10000"
username="root"
password="password"
driverClassName="com.myslq.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mydb"/>
</Context>
이 설정은 'jdbc/MyDB' 라는 JNDI 리소스를 정의하고, 'DataSource' 객체를 생성하여 연결 풀링을 관리합니다.
이 'DataSource' 는 Tomcat 내에서 싱글톤으로 관리됩니다.
4️⃣ 싱글턴 패턴의 이점.
효율성.
여러 서블릿이 동일한 'DataSource' 객체를 공유함으로써 메모리와 자원을 절약할 수 있습니다.
관리의 용이성.
데이터베이스 연결 관리를 중앙화할 수 있으며, 코드에서 직접 관리할 필요 없이 서블릿 컨테이너가 이를 담당합니다.
5️⃣ ✏️ 요약
Java Servlet 컨테이너에서 MySQL 데이터베이스를 연결할 때, 싱글턴 패턴은 주로 DataSource 객체에 적용됩니다.
이 DataSource 객체는 서블릿 컨테이너에 의해 싱글턴으로 관리되며, 데이터베이스 연결 풀을 통해 효율적으로 데이터베이스 연결을 처리합니다.
이를 통해 애플리케이션 전반에 걸쳐 일관되고 성능이 최적화된 데이터베이스 연결 관리가 이루어집니다.
3️⃣ Java 애플리케이션 서버와 MySQL 데이터베이스의 연결 그리고 싱글턴 패턴.
Java 애플리케이션 서버에서 MySQL 데이터베이스를 연결할 때, 싱글턴 패턴은 우요한 역할을 합니다.
그러나 이 패넡은 애플리케이션 코드에서 직접 구현되지 않으며, 애플리케이션 서버나 데이터베이스 연결 관리 라이브러리에서 사용됩니다.
1️⃣ DataSource와 Connection Pooling
Java 애플리게이션 서버(예: JBoss/WildFly, GlassFish, WebSphere)에서 데이터베이스를 연결할 때 일반적으로 'JDBC DataSource' 와 'Connection Pooling' 을 사용합니다.
이때 DataSource 객체는 싱글턴으로 관리되며, 데이터베이스 연결의 효율성을 높이기 위해 연결 풀을 사용합니다.
DataSource 싱글턴 관리
애플리케이션 서버는 데이터베이스와의 연결을 관리하기 위해 DataSource를 생성합니다.
이 DataSource 객체는 서버에서 싱글턴으로 관리됩니다.
즉, 애플리케이션 전반에 걸쳐 동일한 DataSource 객체가 사용됩니다.
DataSource는 내부적으로 데이터베이스 연결 풀을 관리하며, 여러 클라이언트 요청에서 동일한 데이터베이스 연결 객체를 재사용합니다.
Connection 객체 관리
데이터베이스와의 실제 연결을 관리하는 Connection 객체는 매번 새로운 요청이 있을 때마다 DataSource에서 가져오지만, DataSource는 싱글턴으로 관리되므로 전체 애플리케이션에서 일관된 연결 풀이 사용됩니다.
2️⃣ Java EE 환경에서의 DataSource 관리
Java EE 애플리케이션 서버에서는 'JNDI(Java Naming and Directory Interface)' 를 통해 DataSource를 관리합니다.
이는 서버의 전역 설정에서 관리되며, 여러 애플리케이션이 동일한 데이터베이스 연결을 공유할 수 있도록 합니다.
JNDI를 통한 DataSource 설정 예시
```xml
- 이 설정은 애플리케이션 서버가 싱글턴 DataSource 객체를 생성하고 관리하도록 합니다.
### 3️⃣ 싱글턴 패턴의 역할.
- **효율성**
- 싱글턴으로 관리되는 DataSource는 애플리케이션 서버 전체에서 하나의 객체로 유지되며, 이를 통해 메모리와 자원 사용이 최적화됩니다.
- **일관성**
- 동일한 데이터베이스 연결 풀을 사용하기 때문에 애플리케이션 전방에 걸쳐 데이터베이스 연결이 일관되게 관리됩니다.
- **관리 용이성**
- 데이터베이스 연결 관리가 중앙화되어, 각 애플리게이션에서 따로 관리할 필요 없이 서버에서 통합 관리됩니다.
### 4️⃣ EJB와의 통합.
- JavaEE 환경에서 EJB(Enterprise JavaBeans)는 주로 애플리케이션 서버에서 관리되는 비즈니스 로직을 구현하는 데 사용됩니다.
- EJB에서 데이터베이스 연결을 사용할 때도 싱글턴 패턴이 적용된 DataSource를 통해 연결이 이루어집니다.
```java
@Stateless
public class MyService {
@Resource(lookup = "java:/jdbc/MyDB")
private DataSource dataSource;
public void doSomething() {
try (Connection connection = dataSource.getConnection()) {
// 데이터베이스 작업 수행
} catch (SQLException e) {
e.printStackTrace();
}
}
}
이 코드에서 'dataSource' 는 서버에 의해 관리되는 싱글턴 DataSource 객체를 참조하며, 이를 통해 데이터베이스 연결을 처리합니다.
5️⃣ ✏️ 요약,
Java 애플리케이션 서버에서 MySQL 데이터베이스를 연결할 때, 싱글턴 패턴은 DataSource와 같은 중요한 객체 관리에 사용됩니다.
이 패턴을 통해 애플리케이션 서버는 데이터베이스 연결을 효율적이고 일관되게 관리할 수 있으며, 연결 풀링을 통해 자원 사용을 최적화합니다.
애플리케이션 서버가 DataSource를 싱글턴으로 관리함으로써, 서버 전반에 일관된 데이터베이스 연결을 제공하고 효율성을 극대화할 수 있습니다.
Touch background to close