Level 1: 기본 기능 구현 및 코드 개선

1. 코드 개선 퀴즈 - @Transactional의 이해

  • 할 일을 저장하는 API(/todos) 호출 시 Connection is read-only 에러가 발생.
  • 정상적으로 할 일을 저장할 수 있도록 @Transactional 설정을 수정.

2. 코드 추가 퀴즈 - JWT의 이해

  • 요구사항:
    • User 테이블에 nickname 컬럼 추가 (중복 가능).
    • JWT에서 nickname을 꺼내 프론트엔드에 전달.

3. 코드 개선 퀴즈 - AOP의 이해

  • changeUserRole() 메서드 실행 전에 로그를 남기는 AOP 구현.
  • AOP 클래스 AdminAccessLoggingAspect 수정 필요.

4. 테스트 코드 퀴즈 - 컨트롤러 테스트의 이해

  • todo_단건_조회_시_todo가_존재하지_않아_예외가_발생한다() 테스트 실패.
  • 테스트가 정상적으로 통과하도록 수정.

5. 코드 개선 퀴즈 - JPA의 이해

  • 할 일 검색 기능 확장:
    • weather 조건으로 검색 (조건은 선택적).
    • 수정일 기준으로 기간 검색 (기간 조건도 선택적).
  • JPQL을 활용해 조건 처리.

Level 2: 고급 JPA 기능 구현

6. JPA Cascade

  • 할 일을 생성한 사용자가 자동으로 담당자로 등록되도록 cascade 설정.

7. N+1 문제 해결

  • CommentController의 getComments() API에서 N+1 문제 발생.
  • JPA fetch join을 사용하여 문제 해결.

8. QueryDSL

  • JPQL로 작성된 findByIdWithUser 메서드를 QueryDSL로 변경.
  • N+1 문제 없이 데이터 조회 구현.

9. Spring Security

  • 기존 Filter와 Argument Resolver 코드를 Spring Security로 변경.
  • 기존 권한 및 접근 제어 로직 유지.
  • JWT 기반 인증 방식 유지.

Level 3: 도전 과제

10. QueryDSL을 사용한 검색 기능 구현

  • 검색 조건:
    • 제목 키워드 검색 (부분 일치).
    • 생성일 범위 검색 (최신순 정렬).
    • 담당자 닉네임 검색 (부분 일치).
  • 검색 결과:
    • 일정의 제목만 반환.
    • 일정의 담당자 수와 댓글 개수 포함.
  • 기능 요구사항:
    • Projections를 사용해 필요한 데이터만 반환.
    • 페이징 처리된 결과 반환.

 

 

[회고]

 

이번 코드는 기능 개선과 오류의 확인, 간단한 구현이 전부였다. 오히려 내용적인 부분보다는 이론적인 부분을 저오학히 알고 있는지를 확인하는 느낌이 강했던 것 같다. 다만 이번 과제를 하면서 느낀점이, 제대로 하고 있는가?라는 질문이 나왔던 것 같다.

 

현재까지는 이론을 듣고 그저 필요한 기능에 맞춰 구현했지만, 개발자의 필요한 능력은 팀플을 하면서 느낀바로는 문제 해결능력이었다. 더 효율적인 코드를 위해 앞으로의 유연한 확장을 위해 개발을 하는 것이 가장 큰 목표라고 느꼈으며, 코드를 짜는 것 자체는 상당히 중요한 건 맞지만 조금 보조적인 느낌이라고 느꼈다.

 

이렇게 생각하고 나니, 지금 제대로 학습을 하고 있는가?라는 생각이 들 수 밖에 없었다.
1. 왜 이걸 썼는가?

= 레디스는 빠르니까 데이터를 매핑해서 사용하거나 요청하는데 있어 조금이라도 더 속도가 빨라질 것 같아서 사용했다.
2. 레디스는 안정성이 떨어지는데 그 점은 괜찮은가?

= 안정성이 떨어지는 부분을 인지하고 있고 일시적으로만 저장될 데이터만 저장에 이용하였고 처리가 완전히 끝난 완전한 데이터만 Mysql로 저장했다.

 

지금까지는 이렇게 생각했다. 하지만 이건 내가 그나마 많이 사용한 기능이기에 부족하지만 이정도라도 답변이 가능했지만, 사실 생각지 못했던 marven gradle 중 왜 gradle을 사용했는가와 같은 답변에는 대답하기 힘들다는 생각이 들었다. 무엇보다 시간이 많이 없는 상태에서 어떻게 공부하는게 효과적인가 라는 질문이 계속 들었다.

 

이렇게 보니 앞으로의 최종 프로젝트에서 원래는 쿠버네티스, 엘라스틱서치와 같은 기능을 사용하려했지만, 과연 내가 이 기능의 이론이 아니라 정확히 이해하고 언제 사용해야하는지 그리고 각 단점을 어떻게 보완할지와 같은 질문에 대답할 수 있는가? 라는 질문도 생겼다.

 

"이론을 이해하고 적제 적소에 사용할 수 있는 능력, 그리고 각 기능의 단점을 보완하고 장점을 살려 문제를 해결하는 것"이게 현재까지 느끼는 개발자의 능력이었지만, 그저 코드만 짤 뿐이라면 위의 2개의 질문에 답할 수 없다.

 

과연 많은 기능을 포함한 프로젝트를 지금의 이해도로 만들어도 될까?

 

이론을 공부만 해서는 특정 상황을 질문을 하고 해결하라고 하면 답하기 힘들지 않을까?

 

그렇다면 어떻게 공부해야하는가, 아는 친구는 ORM의 작동 방식에 대해서도 상세히 알아야한다고 했다. 사실 자동으로 처리하니 그전까지는 ORM이 어노테이션을 이용해 자동으로 처리하는 것에 대해 아무생각이 없었다. 솔직히 여기까지 공부해야한다고? 생각이 들었다. 어떻게, 어디까지 공부해야하는걸까?

 

이 과제를 진행하면서 3가지가 가장 큰 고민이라는 생각이 든다.

 

'Project > Spring' 카테고리의 다른 글

[Spring] IOC & DI  (0) 2025.01.23
스프링 NEWSFEED 협업 프로젝트  (1) 2024.12.27
일정표를 만들어 보자! 업데이트!  (2) 2024.12.19
일정표를 만들어 보자!  (2) 2024.12.09
[ Spring ] 쇼핑몰 프로젝트 회고  (0) 2024.11.25

IOC와 DI 유의사항

 

1. IOC와 DI의 관계

  • **IOC(제어의 역전)**는 더 큰 개념입니다.
    • 객체의 제어권(생성과 실행 흐름 관리)을 개발자가 아닌 외부(프레임워크, 컨테이너)에 넘기는 설계 원칙입니다.
    • IOC는 객체 지향 설계에서 제어 흐름을 외부로 위임한다는 철학적 개념에 가깝습니다.
  • **DI(의존성 주입)**는 IOC를 구현하는 방식 중 하나입니다.
    • 의존성 주입은 IOC를 실현하는 구체적인 기술적 방법입니다.
    • 객체가 스스로 의존성을 생성하지 않고, 외부에서 필요한 의존성을 주입받는 것을 의미합니다.

즉, DI는 IOC를 구현하기 위한 방법 중 하나이며, 모든 DI는 IOC의 일종이지만, 모든 IOC가 DI는 아닐 수 있습니다.


2. IOC와 DI의 차이

항목 IOC(제어의 역전) DI(의존성 주입)
정의 프로그램의 제어 흐름(객체 생성과 실행)을 개발자가 아닌 외부로 위임하는 설계 원칙. 외부에서 객체의 의존성을 주입하여 결합도를 줄이고 유연성을 높이는 설계 기법.
포커스 제어권(흐름)의 역전: 누가 객체를 생성하고, 실행을 제어할지에 대한 개념적 설계. 의존성(Dependency)의 전달: 객체 간의 관계를 어떻게 설정할지에 대한 구체적인 기술.
범위 설계 철학(개념): 제어권을 외부로 넘기는 큰 틀의 원칙. 구현 방법(기술): 의존성을 외부에서 주입하는 특정한 방식.
실현 방식 DI, 이벤트 기반 시스템, 전략 패턴 등 다양한 방법으로 구현 가능. 주로 DI 컨테이너(Spring, Guice 등)를 사용하거나 수동으로 구현.
목적 결합도를 낮추고 제어 흐름을 외부에서 관리하도록 설계. 구체적인 의존성 전달을 통해 객체 간 관계를 설정.

3. 예시로 이해하기: IOC와 DI

IOC 예시 (제어의 역전)

전통적인 방식 (제어권 개발자 소유):

class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending Email: " + message);
    }
}

class NotificationService {
    private EmailService emailService;

    public NotificationService() {
        // 객체 생성과 의존성을 직접 관리
        this.emailService = new EmailService();
    }

    public void send(String message) {
        emailService.sendEmail(message);
    }
}
  • 문제점:
    • NotificationService가 EmailService의 구체적인 구현체에 강하게 결합.
    • 제어권(객체 생성 및 실행 흐름)이 개발자 코드 내부에 있음.

IOC 적용 (제어권 역전)

class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending Email: " + message);
    }
}

class NotificationService {
    private EmailService emailService;

    // 외부에서 객체를 주입받음 (제어권 역전)
    public NotificationService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void send(String message) {
        emailService.sendEmail(message);
    }
}

class App {
    public static void main(String[] args) {
        // 객체 생성 및 의존성 관리가 외부(App 클래스)로 이동
        EmailService emailService = new EmailService();
        NotificationService notificationService = new NotificationService(emailService);

        notificationService.send("Hello World!");
    }
}
  • 제어권 역전:
    • 객체 생성 및 의존성 설정의 제어권이 App으로 이동.
    • NotificationService는 자신이 사용할 객체를 몰라도 동작.

DI 예시 (의존성 주입)

DI는 위의 IOC를 구현한 구체적인 방식입니다.

// 인터페이스를 통한 DI 적용
interface MessageService {
    void sendMessage(String message);
}

class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending Email: " + message);
    }
}

class SmsService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

class NotificationService {
    private final MessageService messageService;

    // 생성자를 통한 의존성 주입
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // EmailService 주입
        MessageService emailService = new EmailService();
        NotificationService emailNotification = new NotificationService(emailService);
        emailNotification.notify("Hello via Email!");

        // SmsService 주입
        MessageService smsService = new SmsService();
        NotificationService smsNotification = new NotificationService(smsService);
        smsNotification.notify("Hello via SMS!");
    }
}
  • DI 특징:
    • NotificationService는 MessageService 인터페이스에 의존하므로 구체적인 구현체를 알 필요가 없습니다.
    • MessageService의 구현체는 외부에서 주입되므로, 의존성을 변경(예: SmsService → PushService)하기 쉽습니다.

4. IOC와 DI의 일상적인 비유

IOC(제어의 역전):

  1. 택배 서비스:
    • 전통적인 방식: 고객이 직접 가게에서 물건을 사고 가져옵니다. (고객이 제어권을 가짐)
    • IOC 방식: 택배 회사가 물건을 배송하고, 고객은 물건만 받습니다. (제어권이 택배 회사로 역전)
  2. 자동차 서비스:
    • 전통적인 방식: 운전자가 직접 자동차 수리를 관리.
    • IOC 방식: 정비소에 맡기면 정비사가 알아서 자동차를 고침.

DI(의존성 주입):

  1. 셰프와 재료:
    • 전통적인 방식: 셰프가 요리 재료를 직접 구입.
    • DI 방식: 주방 보조가 재료를 준비하고, 셰프는 받은 재료로 요리만 합니다.
  2. 콘센트와 전기:
    • 전통적인 방식: 전자제품이 직접 전기를 생성.
    • DI 방식: 콘센트(외부)가 전기를 공급하고, 전자제품은 연결만 합니다.

5. 요약

항목 IOC(제어의 역전) DI(의존성 주입)
개념 객체 생성과 제어 흐름을 외부로 위임하는 설계 원칙. 외부에서 객체의 의존성을 주입하여 결합도를 줄이는 기법.
관계 큰 철학적 개념(원칙). IOC를 구현하기 위한 구체적인 방법.
목적 객체 간 결합도를 낮추고 제어 흐름을 외부에서 관리. 객체의 의존성을 외부에서 설정해 다형성과 테스트 용이성 확보.
일상 비유 택배 회사가 고객 대신 물건을 배송. 셰프가 주방 보조로부터 재료를 주입받아 요리.

결론: DI는 IOC를 실현하기 위한 방법입니다. IOC는 큰 설계 원칙이고, DI는 그 원칙을 구현하는 하나의 도구입니다. 두 개념은 밀접히 연결되어 있지만, DI는 의존성을 주입하는 구체적인 기술이고, IOC는 객체 제어 흐름을 외부로 넘기는 더 큰 철학적 개념입니다.

 

 

IOC (Inversion of Control) 제어의 역전

 

IOC란?

**IOC(제어의 역전)**은 객체 생성 및 의존성 관리를 개발자가 아닌 외부에서 제어하는 설계 원칙입니다.

  • 기존에는 프로그램이 직접 객체를 생성하고 메서드를 호출하여 제어 흐름을 관리했지만, IOC에서는 이 역할을 외부(조립자, 설정자 또는 프레임워크)가 담당합니다.
  • 이는 객체 간의 결합도를 줄이고, 유연성과 확장성을 높이는 데 도움을 줍니다.
  • 원래는 각 클래스에서 필요한 객체를 생성해서 사용했지만, IOC는 이 객체나 의존성을 담당하는 클래스를 따로 만들고 이 클래스에 다른 클래스가 접근해서 해당 기능을 사용하는 방식이다. 결국 다양성, 결합도 감소를 위해 객체, 의존성을 담당하는 클래스를 따로 만들었다는 말

IOC의 핵심 개념

  1. 객체 생성과 의존성 주입의 외부 위임:
    • 프로그램이 직접 객체를 생성하지 않고, 필요한 객체를 외부에서 받아 사용합니다.
    • 예: 스프링 프레임워크는 컨테이너가 객체를 생성하고 필요한 의존성을 주입합니다.
  2. 제어 흐름의 역전:
    • 기존에는 개발자가 프로그램의 제어 흐름을 관리했지만, IOC에서는 프레임워크나 외부 설정자가 제어 흐름을 관리합니다.
    • 객체는 단순히 요청을 처리하는 역할만 수행하고, 어떤 객체가 주입될지는 알지 못합니다.
  3. 결합도를 낮추고 확장성을 높임:
    • 구체적인 구현체가 아닌 추상화된 인터페이스에 의존하도록 설계하여 다형성을 극대화합니다.

일상적인 예시로 이해하기

  1. 식당 운영 시스템:
    • 전통적인 방식: 셰프가 요리 재료를 직접 고르고, 요리를 만들고, 손님에게 서빙까지 직접 관리.
      • 셰프는 재료에 강하게 의존하므로, 재료가 바뀌면 모든 요리 방식을 수정해야 함.
    • IOC 방식: 식당 주인이 셰프에게 필요한 재료(의존성)를 전달하고, 셰프는 재료를 이용해 요리를 만듦.
      • 셰프는 재료를 직접 고르지 않으며, 어떤 재료가 주어질지 신경 쓰지 않음.
  2. 택시 호출 서비스:
    • 전통적인 방식: 승객이 직접 택시를 잡고, 특정 기사와 거래를 설정.
      • 특정 택시에 의존하므로 변경이 어려움.
    • IOC 방식: 승객은 호출 앱(플랫폼)을 사용해 택시를 요청하고, 플랫폼이 적합한 택시를 배정.
      • 승객은 배정된 기사에 대해 미리 알 필요가 없고, 플랫폼이 적절한 택시를 알아서 선택.
  3. 전자제품과 전원:
    • 전통적인 방식: 전자제품(객체)이 직접 전원을 생성.
      • 전자제품마다 전력 공급 방식이 달라지면 제품 내부를 수정해야 함.
    • IOC 방식: 전자제품은 콘센트에서 제공되는 전원을 이용.
      • 전자제품은 전원의 구체적인 공급 방식을 몰라도 작동 가능.

 

 

그래서 흐름 제어? 는 뭔 말일까?

 

**"실행을 제어한다"**는 말은 프로그램에서 어떤 객체가 메서드를 호출하고, 로직을 진행하며, 전체 흐름을 주도할지를 결정하는 주체가 누구인지를 의미합니다. 전통적인 방식에서는 개발자가 직접 객체를 생성하고 필요한 메서드를 호출하며 실행 흐름을 주도했습니다. 반면, **IOC(제어의 역전)**에서는 이 제어 흐름이 외부 프레임워크나 컨테이너로 넘어갑니다. = 기존에는 프로그래머가 클래스에서 어떤 클래스를 불러와서 실행하고 ~~~ 와 같은 실행의 흐름을 직접 구성해주었다면, 스프링에서는 이걸 스프링 프레임워크가 대신한다. 흔히 사용하는 어노테이션과 생성자의 객체 매개변수를 통해 DI를 수행한다.

 

+ 생성자의 객체 매개변수 의 경우 클래스 내부에서 객체를 정의하는 것이 아닌 외부 클래스에서 객체를 만들고 그걸 객체 매개변수 로 인식하기만 하니 이것또한 IOC에 해당한다.

 

+ "의존성을 주입받는다"는 말은, 클래스가 직접 필요한 객체를 생성하지 않고, 외부에서 만들어진 객체를 전달받아 사용한다 = 외부 객체에 의존하겠다. 이말이다.


1. 실행 흐름의 제어란?

전통적인 방식 (제어 흐름 개발자 소유):

  • 개발자가 직접 모든 객체를 생성하고, 그 객체의 메서드를 호출하여 실행을 제어합니다.
  • 실행의 주체는 개발자 코드 내부에 있습니다.

IOC 방식 (제어 흐름 외부 위임):

  • 객체의 생성 및 실행 흐름(메서드 호출의 순서와 방식)을 외부 컨테이너(예: 스프링)에 맡깁니다.
  • 개발자는 객체의 실행 방법이나 관계를 정의하지 않고, 외부에서 주입된 객체에만 집중합니다.
  • 실행의 주체가 외부로 넘어가므로 "제어의 역전"이 이루어진 것입니다.

2. 코드로 이해하기

(1) 전통적인 방식: 실행 흐름을 개발자가 직접 관리

class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending Email: " + message);
    }
}

class NotificationService {
    private EmailService emailService;

    public NotificationService() {
        // 객체 생성 및 관계 설정
        this.emailService = new EmailService();
    }

    public void notify(String message) {
        // 실행 흐름을 직접 제어
        emailService.sendEmail(message);
    }
}

public class Main {
    public static void main(String[] args) {
        NotificationService notificationService = new NotificationService();
        notificationService.notify("Hello, World!");
    }
}

실행 흐름 설명:

  1. Main 메서드에서 개발자가 NotificationService 객체를 생성.
  2. NotificationService는 내부적으로 EmailService 객체를 생성.
  3. notify 메서드를 호출하며, 실행 흐름을 개발자가 직접 제어.

문제점:

  • 모든 객체의 생성, 관계 설정, 실행 흐름을 개발자가 직접 관리하므로 코드가 복잡해짐.
  • 다른 의존성(SmsService)으로 변경하려면, 개발자가 기존 코드를 수정해야 함.

(2) IOC 방식: 실행 흐름을 외부 컨테이너에 위임

interface MessageService {
    void sendMessage(String message);
}

class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending Email: " + message);
    }
}

class SmsService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

class NotificationService {
    private MessageService messageService;

    // 의존성을 외부에서 주입받음
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        // 외부에서 주입된 객체를 사용해 실행 흐름을 진행
        messageService.sendMessage(message);
    }
}

class AppConfig {
    // 외부 컨테이너 역할: 객체 생성 및 관계 설정
    public static NotificationService createNotificationService(String type) {
        MessageService messageService;
        if (type.equalsIgnoreCase("email")) {
            messageService = new EmailService();
        } else if (type.equalsIgnoreCase("sms")) {
            messageService = new SmsService();
        } else {
            throw new IllegalArgumentException("Unknown type");
        }
        return new NotificationService(messageService);
    }
}

public class Main {
    public static void main(String[] args) {
        // 실행 흐름을 AppConfig(외부 컨테이너)가 관리
        NotificationService notificationService = AppConfig.createNotificationService("email");
        notificationService.notify("Hello via Email!");

        NotificationService smsNotification = AppConfig.createNotificationService("sms");
        smsNotification.notify("Hello via SMS!");
    }
}

실행 흐름 설명:

  1. AppConfig가 객체를 생성하고 의존성을 주입하여 실행 흐름을 관리.
  2. NotificationService는 어떤 구체적 객체가 사용되는지 알 필요 없이, 주입된 객체만 사용.
  3. Main 메서드는 AppConfig를 호출하여 필요한 객체를 가져오고 실행.

3. "실행 흐름을 외부로 넘긴다"의 본질

IOC에서 실행 흐름이 외부로 넘어간다는 것은 다음과 같은 점을 포함합니다:

  1. 객체 생성:
    • 개발자가 객체를 직접 생성하지 않고, 외부 컨테이너가 생성.
    • 개발자는 컨테이너가 제공한 객체를 사용만 함.
  2. 의존성 관리:
    • 개발자가 객체 간의 관계를 직접 설정하지 않음.
    • 외부 컨테이너가 필요한 객체를 주입하여 의존성을 관리.
  3. 실행 순서 제어:
    • 실행되는 메서드나 로직의 흐름을 외부에서 관리.
    • 개발자는 "어떤 객체가 주입될지", "어떻게 동작할지"를 신경 쓰지 않고, 주어진 역할에만 집중.

4. 일상적인 비유로 이해

전통적인 방식 (개발자가 직접 제어):

  • 상황: 개인이 직접 집을 짓는 경우
    1. 필요한 재료(의존성)를 직접 구매.
    2. 벽돌을 쌓고, 전기 배선을 연결하며, 도배까지 직접 실행.
    3. 모든 과정(흐름)을 스스로 제어.

IOC 방식 (실행 흐름 외부 위임):

  • 상황: 건축회사를 고용하여 집을 짓는 경우
    1. 건축회사(컨테이너)가 필요한 재료를 관리.
    2. 전기공, 배관공, 목수를 투입(의존성 주입)하여 작업.
    3. 집주인은 "이런 집을 원한다"는 요구만 하고, 실행 흐름은 건축회사가 관리.

5. 결론: 실행 흐름의 역전이란?

  • 전통적인 방식에서는 개발자가 객체 생성, 관계 설정, 메서드 호출 등 모든 실행 흐름을 직접 관리.
  • IOC에서는 실행 흐름(객체 생성, 의존성 주입, 메서드 호출 등)을 외부 컨테이너(예: 스프링 프레임워크)에 위임.
  • 개발자는 객체 간의 관계나 실행 흐름을 신경 쓰지 않고, 주어진 역할에만 집중할 수 있습니다.

즉, "실행을 제어한다"는 것은 객체 생성, 의존성 설정, 로직 실행 흐름을 포함하며, IOC에서는 이를 외부에서 관리하여 더 유연하고 확장 가능한 구조를 제공합니다.


IOC의 장점

  1. 유연성:
    • 새로운 기능 추가 시 기존 코드 수정이 필요 없음(OCP 원칙 준수).
  2. 결합도 감소:
    • 객체 간의 관계를 인터페이스로 추상화하여 변경에 강함.
  3. 테스트 용이성:
    • Mock 객체를 쉽게 주입하여 테스트 가능.
  4. 코드 재사용성:
    • 객체를 재사용할 수 있어 개발 속도가 빨라짐.

IOC 요약 표

항목 내용
정의 객체의 생성 및 의존성 관리를 개발자가 아닌 외부에서 제어하는 설계 원칙.
핵심 개념 1. 객체 생성 및 관리 외부 위임2. 제어 흐름 역전3. 결합도 감소와 다형성 극대화
장점 유연성, 유지보수 용이, 결합도 감소, 테스트 용이성, 코드 재사용성 증가
일상적인 예시 1. 식당에서 주인이 재료를 제공2. 택시 호출 앱3. 전자제품과 콘센트
대표적인 활용 프레임워크 스프링 프레임워크 (DI 컨테이너를 활용해 IOC 구현)

결론

IOC는 객체 지향 설계에서 **의존성 주입(DI)**과 함께 사용되어 프로그램의 유연성과 확장성을 극대화합니다. 이는 객체 간의 강한 결합을 제거하고, 코드 변경 없이 새로운 기능을 추가하거나 기존 기능을 교체할 수 있게 합니다.
일상 속 예시와 코드를 함께 보면, IOC는 **"중간에서 객체 관계를 설정하고 관리하는 조립자 역할을 외부에 위임"**하는 개념임을 쉽게 이해할 수 있습니다.

 

 

DI (Dependency Injection) 의존성 주입

 

1. DI란?

**DI(의존성 주입)**는 객체 간의 의존성을 외부에서 주입하여 객체 간의 결합도를 줄이고 유연성을 높이는 설계 원칙입니다.

  • 의존성(Dependency): 한 객체가 다른 객체를 사용할 때 그 객체를 의존한다고 합니다.
  • 의존성 주입(Injection): 의존하는 객체를 코드 내부에서 직접 생성하지 않고, 외부에서 주입받는 방식입니다.

2. DI의 핵심 개념

  1. 의존성 분리:
    • 객체는 필요한 의존성을 스스로 생성하지 않습니다.
    • 대신 외부에서 의존성을 주입받아 사용합니다.
  2. 유연성과 재사용성:
    • 객체가 구체적인 구현체가 아닌 인터페이스에 의존하므로, 의존성을 쉽게 교체할 수 있습니다.
    • 테스트 시 Mock 객체를 주입하거나, 새로운 구현체를 추가하는 것이 용이합니다.
  3. DI의 유형:
    • 생성자 주입(Constructor Injection): 의존성을 생성자를 통해 주입.
    • 세터 주입(Setter Injection): 세터 메서드를 통해 의존성을 주입.
    • 필드 주입(Field Injection): 필드에 직접 주입(주로 프레임워크에서 사용).

3. DI의 일상적인 예시

  1. 가전제품과 콘센트:
    • 의존성: 가전제품(객체)은 전기(의존성)가 필요합니다.
    • 전통적인 방식: 가전제품이 자체적으로 전기를 생성해야 합니다.
    • DI 방식: 가전제품은 콘센트(외부 주입)에서 전기를 받아 사용합니다. 가전제품은 전기의 세부 공급 방식(예: 태양광, 수력)을 몰라도 동작합니다.
  2. 택배 서비스:
    • 의존성: 고객은 물건을 받으려면 택배 서비스(의존성)가 필요합니다.
    • 전통적인 방식: 고객이 직접 물건을 구매하고 배달까지 처리.
    • DI 방식: 택배회사가 물건을 고객에게 전달. 고객은 물건이 어디서 왔는지 알 필요 없이, 사용만 하면 됩니다.
  3. 식당의 셰프와 재료:
    • 의존성: 셰프는 요리를 하려면 재료가 필요합니다.
    • 전통적인 방식: 셰프가 직접 재료를 구하고 요리를 합니다.
    • DI 방식: 주방 보조(외부 주입)가 재료를 제공하고, 셰프는 재료를 받아 요리만 합니다. 셰프는 재료가 어디서 왔는지 몰라도 요리를 할 수 있습니다.

4. 코드로 이해하기

1) 전통적인 방식

class EmailService {
    public void sendEmail(String message) {
        System.out.println("Sending email: " + message);
    }
}

class NotificationService {
    private EmailService emailService;

    public NotificationService() {
        // 직접 의존성 생성
        this.emailService = new EmailService();
    }

    public void notify(String message) {
        emailService.sendEmail(message);
    }
}

public class Main {
    public static void main(String[] args) {
        NotificationService notificationService = new NotificationService();
        notificationService.notify("Hello World!");
    }
}

문제점:

  1. NotificationService가 EmailService라는 구체적인 구현체에 강하게 결합되어 있습니다.
  2. 새로운 알림 방식(예: SmsService)을 추가하거나 테스트 시 Mock 객체로 대체하기 어려움.

2) DI 방식: 생성자 주입

// 의존성을 추상화
interface MessageService {
    void sendMessage(String message);
}

// 다양한 구현체
class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending email: " + message);
    }
}

class SmsService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

class NotificationService {
    private final MessageService messageService;

    // 생성자를 통해 의존성을 주입받음
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // 의존성을 외부에서 주입
        MessageService emailService = new EmailService();
        NotificationService notificationService = new NotificationService(emailService);
        notificationService.notify("Hello via Email!");

        // 의존성을 다른 구현체로 교체
        MessageService smsService = new SmsService();
        NotificationService smsNotificationService = new NotificationService(smsService);
        smsNotificationService.notify("Hello via SMS!");
    }
}

특징:

  • NotificationService는 MessageService 인터페이스에만 의존합니다.
  • 의존성(EmailService나 SmsService)은 외부에서 주입되므로, NotificationService는 구현체가 무엇인지 알 필요가 없습니다.

3) DI 방식: 스프링 프레임워크를 활용

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

// 의존성을 추상화
interface MessageService {
    void sendMessage(String message);
}

@Component
class EmailService implements MessageService {
    public void sendMessage(String message) {
        System.out.println("Sending email: " + message);
    }
}

@Component
class NotificationService {
    private final MessageService messageService;

    @Autowired
    public NotificationService(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        var context = SpringApplication.run(Main.class, args);

        NotificationService notificationService = context.getBean(NotificationService.class);
        notificationService.notify("Hello via Email!");
    }
}

특징:

  1. 스프링 컨테이너가 객체를 생성하고, 의존성을 자동으로 주입합니다.
  2. 개발자는 객체 생성과 의존성 주입의 번거로움에서 벗어나 비즈니스 로직에 집중할 수 있습니다.

5. DI의 장점

  1. 유연성: 의존성을 쉽게 교체할 수 있어 확장 가능성이 높습니다.
  2. 결합도 감소: 객체 간의 강한 결합을 제거하여 코드의 재사용성과 유지보수성을 높입니다.
  3. 테스트 용이성: Mock 객체를 주입하여 단위 테스트를 쉽게 수행할 수 있습니다.
  4. 코드 가독성: 각 클래스가 자신의 역할에만 집중하므로 코드가 간결해집니다.

6. 요약 표

항목 내용
정의 객체 간의 의존성을 외부에서 주입받아 결합도를 낮추고 유연성을 높이는 설계 방식.
핵심 개념 1. 객체가 직접 의존성을 생성하지 않고 외부에서 주입받음2. 결합도 감소 및 다형성 극대화
DI의 유형 생성자 주입, 세터 주입, 필드 주입
일상적인 예시 1. 가전제품과 콘센트2. 택배 서비스3. 식당의 셰프와 재료
장점 유연성 증가, 결합도 감소, 테스트 용이성, 코드 재사용성 및 가독성 향상
대표적인 활용 프레임워크 스프링 프레임워크 (DI 컨테이너를 통해 자동 의존성 주입)

7. 결론

DI는 객체 간의 의존성을 외부로 분리하여 개발자가 결합도를 낮추고, 코드를 더 유연하게 설계할 수 있도록 돕습니다. 이를 통해 객체는 자신의 역할에 집중하고, 의존성 관리의 복잡성은 외부 컨테이너(프레임워크)가 처리하게 됩니다. 일상적인 예시와 코드로 이해하면 DI는 "객체에 필요한 것을 외부에서 제공받아 유연하고 확장 가능한 구조를 만든다"는 개념으로 쉽게 이해할 수 있습니다.

 

**IOC(제어의 역전)**와 **DI(의존성 주입)**는 밀접하게 연관되어 있지만, 완전히 같은 말은 아닙니다. 둘의 관계는 다음과 같이 설명할 수 있습니다:

 

 

 

GitHub - sparta-sixsense/newsfeed

Contribute to sparta-sixsense/newsfeed development by creating an account on GitHub.

github.com

 

 

 

 

KEEP

📌 현재 만족하고 계속 이어가고 싶은 = 유지할 부분

  • 나를 위한 커밋이 아니라 팀원을 위한 커밋을 하기 위해 메시지 내용을 어떻게 쓸 지 고민해
  • 프로젝트를 진행하면서 활용하면 좋을 것 같은 다양한 로직과 기술을 사용하고 공유함 (코드 컨벤션, 소스패키지 구조 시각화 등)
  • 정규표현식과 regxp 를 사용해 입력패턴을 적용해 봄
  • 팀의 분위기를 긍정적으로 이끌어가기 위해 노력함
  • 깃 컨벤션을 미리 정해놓은 부분이 좋았음
  • 코드의 안정성을 위해 공통 기능을 우선적으로 준비하고 프로젝트를 시작했던 점이 추후 도움이 되었음
  • 비즈니스 로직 간소화
  • 검증 로직은 별도 메서드로 이원화
  • 팀원들과 화목한 관계 추구 노력

 

 

PROBLEM

📌 불편하게 느꼈고 수정하고 싶은 부분 = 문제였던 부분

  • 선행학습이 부족해 다른 팀원이 사용하는 기술을 이해하지 못함
  • 계획을 세우지 않아 시간을 효율적으로 관리하지 못하고, 개인 학습 시간을 전혀 가지지 못함
  • 깃허브 커멋 컨벤션이 엄격하게 지켜지지 않음
  • 테이블 설계시, 처음에 고려치 못한 부분에 대해 우선순위에서 밀렸다는 이유로 반영 못함
  • 다른 브랜치에 푸쉬하는 실수를 저질렀음
  • 실제 애플리케이션 배포까지 경험해보자라는 목표로 프로젝트에 임했지만 AWS에 대한 이해 부족으로 배포 단계까지 가지 못함
  • 예외 처리를 다소 복잡함. 어느 수준까지 분기할 건지 고민 필요
  • API Url 설계할 때 조금 더 RESTful하게 만들 필요가 있음. url path만 보고 어떤 역할을 하는지 이해하기 쉽지 않은 API 다수
  • 소규모 프로젝트라, 패키지 구조를 [controller, service, repository, domain, dto ...] 이런 식으로 만들었는데, 생각보다 DTO 클래스가 많아 조금만 더 프로젝트 규모가 커지면 유지보수 불가. domain 단위 패키지 구조로 마이그레이션 고려

 

 

TRY

📌 문제 해결을 위해 실행 가능한 것들 = 앞으로의 목표

  • 혼자 해보기엔 어려웠던 기능을 팀원들과 협업하면서 시도해보기
  •  
  • 계획표를 꼭 세우고, 개인 학습 시간 챙기기
  • 다음에는 컨벤션을 더 확실히 정하고 프로젝트를 시작하기
  • 타 팀의 잘한 점을 최대한 흡수하기, 새로운 인사이트를 얻기
  • 우선은 필요 기능을 모두 구현하고, 디테일을 살리기
  • 데이터베이스 설계나 sql에 대해 공부를 더 해야겠다는 생각이 들었음
  • 기능을 왜 쓰는지, 어떻게 쓰는건지 정확히 파악하는 것이 가장 중요하다는 것을 알게됨
  • 다음 프로젝트에서는 간트 차트를 작성해 보고자 함
  • 테스트 코드를 작성
  • 더미데이터를 정합성 있게 제조
  • 테스트 코드가 github에서 자동으로 빌드되고, 애플리케이션이 자동으로 서버에 배포되는 단계까지 적용
  • AWS, Docker, 쿠버네티스, 엘라스틱 서치, 레디스, 카프카 적용
  • 깃에 대한 상세 학습

 

[ 참고 자료 ]

 

팀 문화의 탄생 | 우아한형제들 기술블로그

안녕하세요, 우아한형제들 상품시스템팀의 손권남입니다. 가끔씩 저는 우리팀의 팀 문화에 대한 질문을 받곤 합니다. 그때마다 매번 단편적인 답을 드리곤 하면서 한 번 정도 우리의 팀문화를

techblog.woowahan.com

 

 

'Project > Spring' 카테고리의 다른 글

Spring Querydsl 과제 회고  (1) 2025.01.27
[Spring] IOC & DI  (0) 2025.01.23
일정표를 만들어 보자! 업데이트!  (2) 2024.12.19
일정표를 만들어 보자!  (2) 2024.12.09
[ Spring ] 쇼핑몰 프로젝트 회고  (0) 2024.11.25

트러블 슈팅

1. 배경

  • 웹 개발 숙련에 입문하였다.
  • 사용 기술은 jpa와 쿠키,세션이 추가되었다.
  • 웹 개발의 기초에서 진행하였던 일정표 프로젝트를 개량하는 것이 발제였다.

 

2. 발단

  • 처음은 아니였던 만큼 머리속에 웹 개발의 전재라인이 떠올랐던 점이 크게 작용했다.
  • 확실히 저번보다는 편하게 진행이 가능했다는 점이 조금은 성장했다는 것을 느낄 수 있었다.
  • 하지만 문제가 없지는 않았다.
  • 프론트와 함께 쓰는것을 목표로 했다보니, 자연스럽게 프로젝트도 프론트를 구성했었는데, 튜터님의 조언이 백엔드에 치중해서 코드를 작성해보라는 내용이 있다는 것을 너무 늦게 파악했다..
  • 코드적인 문제로는, 중간에 프론트에서 데이터를 한번에 2가지 servlet으로 전송하려했는데 그게 좀처람 잘 되지 않았다.
  • 그 외에도 세션을 사용하려다가 생기는 오류가 많아 애를 먹었다.

 

3. 전개&위기

  • 여전히 문제는 저 2개였다. controller와 service, 전에는 controller와 repository에 모든 기능을 다 넣었지만 이번에는 service를 추가해보는 것이 좋을 것 같다는 조언에 의해 나눠서 처리했다.
  • 이번에는 확실히 처리하는 파일과 저장하는 파일이 따로 존재하다보니 코드의 수정에서 굉장히 편하다는 것을 느낄 수 있었고, 일단 코드를 굳이 엄한데에서 안 찾아도 되었다는 점이 굉장히 편했다.
  • 다만, 세션의 filter의 경우 처음하는 부분이다보니 많이 해매였던 것 같다. 
  • 코드 자체가 인증, 인가라고는 하지만 막상 해보기 전까지는 어떤 방식의 구현인지 이해가 정확히되지 않았다는 점이 컸던 것 같다.

 

5. 절정

  • 일단 처음에는 받았던 강의 내용을 그대로 사용해 보다가 gpt를 통해 업그래드를 진행해 보았다. 내용은 간단히 받은 세션과 내용의 일치를 통한 간단한 절차를 구현했지만, 뭔가 많이 부족해보인다.
  • 대부분의 문제의 경우 bean이나 다양한 파트에서 자동으로 지원해 준다는 점에서 간편하게 구현이 가능했고 처음에 많이 해메이던 부분은 한번 하고나니 확실히 익숙해지는 것이 느껴졌다.

 

6. 결말

  • 이번에는 계속 집 수리로 인해 시간을 너무 많이 잃었던 점이 코드를 작성하지 못한 큰 이유가 되었다.
  • 강의도 정말 일단 가능한곳까지는 해보자는 생각으로 추가학습과 주말도 사용했지만, 생각보다 기공분들이 너무 많이 찾아오셨고 체크를 담당하고 설명을 듣고 부모님께 전달하는 시간이 너무 길었다.
  • 앞으로도 계속 이럴 수 없다는 생각이 먼저들었다. 
  • 동생과 분담하는 방식으로 일정을 조절해야지, 안그러면 팀프로젝트에서도 민폐를 끼칠 것 같다.
  • 코드는 단순히 있는 코드를 변형하는 방식이라 jpa자체는 엄청 힘들지는 않았지만, 처음해보는 세션로그인은 조금 해메였던 것 같다.
  • 처음에 걱정하던 부분은 머리속에 개발 로드맵이 어느정도 구성되고 나니 좀 불안이 덜어진 것 같다.
  • 다만 postman과 테스트코드가 없다는 점에서 앞으로도 문제는 해결할 필요가 있어보인다.
  • 프론트를 일부러 같이 가져가려고 했지만, 그렇게 좋은 방법은 아니였다는 조언이 있었던 만큼, 차라리 프론트를 만들 시간에 다른 백엔드를 좀 더 가공하려 하는 방식으로 바꿔야할 것 같다.

'Project > Spring' 카테고리의 다른 글

Spring Querydsl 과제 회고  (1) 2025.01.27
[Spring] IOC & DI  (0) 2025.01.23
스프링 NEWSFEED 협업 프로젝트  (1) 2024.12.27
일정표를 만들어 보자!  (2) 2024.12.09
[ Spring ] 쇼핑몰 프로젝트 회고  (0) 2024.11.25

트러블 슈팅

1. 배경

  • 드디어 웹 개발의 기초에 입문하였다.
  • 사용 기술은 spring, jsp, jdbc, mysql이다.
  • 웹 개발의 기초를 위한 일정표를 만드는 발제를 진행하였다.

2. 발단

  • 처음부터 끝까지 문제가 상당히 많았다.
  • 데이터의 타입 정의는 문제가 없었다
  • 예외처리에서 예외처리가 무한으로 재귀되는 문제가 있어 확인해 보았다.
  • 매핑 형식에 대한 오류가 등장하였다.
  • 데이터 로드 형식, 범위에 대한 문제가 발생하였다.
  • SQL문의 경우 오랜만에 사용하다보니, 조금 익숙한 감이 떨어졌다.

3. 전개&위기

1. Service

  • 이 파트는 단순히 클라이언트의 요청을 처리하는 파트였어서 큰 문제는 없었다.
  • 다만 지금 보니 파일이나 폴더명이 service로 되어있어야했다.

2. exception

  • 위와 같이 대분의 오류를 판단하고 메시지 형태로 페이지에 띄우는 코드를 작성했다.
  • 다만 여기서 문제가 있었던 것이, 맨 처음에는 페이지로 연동을 안 시켜주어서 에러를 띄우기 시작하였다.

3. controller

  • 여기 파트의 경우 데이터를 불러오고 처리하는 직접적인 메인에 가까운 코드였다.
  • 여기서는 다양한 문제가 발생하였는데, 데이터를 외래키를 사용하기 위해 테이블을 2개로 나누고나니, 요청에서 문제가 다수 발생하였다.
  • 그 이외에도 post,get만 사용하였지만 다양한 방식을 사용하지 못했다는 점도 문제로 잡혔다.
  • 가장 큰 문제는 사실 위의 문제보다는 2개가 나타났었다
  • 하나는, 그저 시연에 있던 코드를 정확히 이해하지 못하고 치기만 했다는 점에서 이해가 부족해 보인다.
  • 둘, 여기는 서버라서 그런건지 인터럽트가 안걸린다. 이게 엄청난 시간을 딜레이 시킨 요인이 되기 시작했다.

4. repository

  • 데이터를 불러오는 형식이나 양, 직접적인 jdbc를 이용한 sql문 처리를 담당했다.
  • 여기서 가장 큰 문제는 sql일것이라 생각했으나, 이미 해본 적이 있던 부분이라 차라리 할만했다.
  • 오히려 외래키가 들어오고 나서 부터 생기는 join 문제가 발목을 잡았다.
  • 가장 큰 문제였던 점은, mysql에서는 직접 데이터를 불러오고 삭제하지만 여기서는 다른 java코드에서 요청하다보니 정확히 어디서 요청하고 어디서 문제가 난 것인지 몰랐다는 점이 너무 시간을 잡아먹었다.

  • 여기는 딱히 막히는 부분은 없었지만, 저 위의 태그의 사용방식을 정확히 알지 못한게 좀 시간을 잡아먹었다.

5. 절정

1. controller

  • 처음에는 잘 몰랐지만, 하면서 controller의 역활은 생각보다 복잡하면서도 명확하다는 것을 파악할 수 있었다.
  • 우선 필요한 데이터를 수집하고, 데이터를 통해 문제를 처리한다. 혹시모를 예외를 처리하면서 다른 코드와 가장 밀접하게 많이 연동되는 곳이라는 것을 파악할 수 있었다.
  • 과제의 내용에 외래키를 꼭 사용하는 것이 포함되어 있었다보니, 원래의 코드를 수정하여 postRepository를 2가지 테이블에 같이 걸어 데이터를 제공받는 코드로 작성하였다.
  • 예외는 exception 테이블에 있는 GlobalExceptionHandler에서 처리할 수 있도록 예외를 해당 클래스와 연동된 페이지로 페이지 이동을 걸어두었다.
  • 모든 클래스는 요청하는 것이 어떤 데이터인지 형식인지의 차이는 있었지만 위의 형태를 유지하도록 코드를 구성했다.

 

2. repository

  • 여기서는 조인외에는 거의 다 데이터베이스 자체적인 오류로 인해 문제가 많이 발생하였다.
  • 외래키를 첨가하고 나서부터, user_id라는 외래키를 직접 입력을 받는 형식을 처음에는 사용하였으나,
  • 사실 옆 테이블의 기본키라는 것을 깨닫고 그냥 옆에서 데이터를 불러오는 형식으로 바꾸려 했더니 기존 코드와 충돌이 어마무시하게 일어났다.
  • user_id가 입력이 안되어서 무결성 오류로 데이터가 저장이 안되는 경우부터, 데이터가 한 테이블만 저장되고 다른 테이블에는 외래키로 인해 저장이 안되는 등 여러 문제가 있었다.
  • 결론적으로 대부분 외래키에 대한 무결성 보존 오류였다. 이 점은 항상 주의하는 수 밖에 없을 것 같다.

 

3. exception

  • 여기서 의존성 문제가 가장 많이 등장했던 것 같다. 
  • 다만 이 부분은 코딩적인 문제라기보단, 지식의 문제라 많이 하다보면 괜찮을 것 같다.

 

6. 결말

  • 이번 코드에서 걱정했던 클린 코드의 문제가 조금씩 드러나고 있는 것 같다.
  • DB,서버,클라 이렇게 3개가 통신하다보니 더 코드가 복잡하고 읽어내기 힘들었다고 생각한다.
  • 왜 사람들이 폴더를 정확히 나눠서 코드를 짜라는지 알 수 있었고 앞으로도 처음부터 잘 배치하는 것을 목표로 해야할 것 같다는 생각이 들었다.
  • 이번 코드하면서 테스트를 하면서 하진 않았다. 다만, 서버의 경우 반드시 테스트를 통해서 점검을 해야하는 점을 생각하면 추후 반드시 한번은 해봐야겠다.
  • 위의 에러들이 가장 알기 어려웠지만, 지금와서 보면 체계를 잘 잡는게 중요한 것 같다.
  • 웹 개발의 로드맵을 머리에 저장해 두면 앞으로 이런 문제는 좀 덜 생길 것 같다.
  • 하면서 가장 어려웠던게 어디서부터 어떤 순서로 만들어야하는지 감이 전혀 안왔기 때문이다.
  • 각 프로그램들이 하는 일에 대한 정의가 머리에서 흐릿하게만 떠올라서 배치하는 것 조차 어려웠기 때문이다.
  • 그리고 다양한 개념이 갑자기 쓰이다보니, 또 머리속에서 개념이 흔들리고 있다. 이 부분도 함께 채워야할 것 같다.

 

'Project > Spring' 카테고리의 다른 글

Spring Querydsl 과제 회고  (1) 2025.01.27
[Spring] IOC & DI  (0) 2025.01.23
스프링 NEWSFEED 협업 프로젝트  (1) 2024.12.27
일정표를 만들어 보자! 업데이트!  (2) 2024.12.19
[ Spring ] 쇼핑몰 프로젝트 회고  (0) 2024.11.25

★요구사항 분석과 기초 설계의 중요성

  • 코드가 정말 길어지고 나니, 처음에 설계를 잘못했을 때 너무너무 큰 대가를 치르게 되었다.
  • 맨 처음에 요구사항을 정확히 분석하고 완전한 설계를 해두어야 추후 문제가 없다.

백엔드 프로젝트의 준비물

  • 자바, 스프링, JPA, 서버 사이드 템플릿(Thymeleaf), DB

백엔드 프로젝트의 구성

  • resources : 프론트엔드 + 애플리케이션 설정 파일

  • Java : 백엔드 전반

엔티티( Entity) & 빈(Bean)

  • 1. 엔티티(Entity)
    • 정의: 엔티티는 주로 데이터베이스 테이블에 매핑되는 객체입니다. 데이터베이스에서 특정 테이블과 일대일 매핑을 하여, 해당 테이블의 데이터를 객체로 다룰 수 있게 해줍니다.
    • 용도: 엔티티는 주로 **JPA(Java Persistence API)**와 함께 사용되며, 데이터베이스의 데이터를 객체지향적으로 처리하는 역할을 합니다.
      • 예를 들어, @Entity 어노테이션을 사용하여 JPA 엔티티 클래스를 정의하면, 이 클래스는 데이터베이스 테이블과 연관됩니다.
      • 엔티티 클래스는 객체 상태와 데이터베이스 상태 간의 매핑을 담당합니다. 즉, 객체의 필드 값이 데이터베이스의 열(column)과 매핑됩니다.
    • 특징:
      • 데이터베이스와 연결되어 있으며, 데이터의 저장, 조회, 수정, 삭제 등의 작업을 할 수 있습니다.
      • 일반적으로 @Entity 어노테이션으로 클래스가 엔티티로 선언됩니다.
      • 엔티티는 보통 영속성 관리의 대상이 되어 JPA나 Hibernate가 객체의 생명 주기를 관리합니다.
    2. 빈(Bean)
    • 정의: **빈(Bean)**은 Spring에서 사용되는 객체로, Spring IoC(제어의 역전) 컨테이너에 의해 관리되는 객체입니다. Spring에서는 Bean을 관리하고 생성하는 방식으로 객체 간의 의존성을 관리합니다.
    • 용도: 빈은 애플리케이션의 구성 요소로, 객체의 생성, 의존성 주입(DI), 생명주기 관리 등을 Spring 컨테이너가 담당합니다.
      • Spring 컨테이너에 의해 관리되므로, 애플리케이션에서 필요한 객체를 명시적으로 생성하거나 관리할 필요 없이 Spring이 자동으로 빈을 관리하고 주입합니다.
      • 빈은 애플리케이션의 비즈니스 로직을 담당하는 서비스나 컴포넌트 클래스일 수 있습니다.
    • 특징:
      • 빈은 @Component, @Service, @Repository, @Controller 등의 어노테이션을 사용하여 선언할 수 있습니다.
      • Spring IoC 컨테이너는 이러한 빈들을 자동으로 관리하고, 의존성 주입을 통해 필요한 곳에 제공해 줍니다.

영속성

 

영속성의 개념

영속성은 객체 지향 프로그래밍(OOP)에서 메모리 상의 객체가 영구 저장소(주로 데이터베이스)에 저장되는 과정과 관련이 있습니다. 즉, 프로그램의 실행이 종료되더라도 데이터가 계속해서 유지될 수 있게 하는 것을 의미합니다.

JPA에서의 영속성

**JPA(Java Persistence API)**에서 영속성은 객체가 데이터베이스 테이블과 매핑되어 데이터베이스에 저장되거나, 수정되고, 삭제되는 과정을 관리하는 기능입니다. JPA는 자바 애플리케이션에서 데이터베이스와의 상호작용을 객체 지향적으로 처리할 수 있게 해주는 API입니다.

JPA 영속성의 주요 개념

JPA에서는 **엔티티 객체(Entity Object)**를 사용하여 데이터를 저장하고, 이 엔티티 객체들이 **영속성 컨텍스트(Persistence Context)**라는 환경 내에서 관리됩니다. 영속성 컨텍스트는 영속성 상태를 관리하는 메모리 내의 저장소로, JPA가 객체와 관계형 데이터베이스 간의 매핑을 관리합니다.

 

 

JPA를 사용한 이유

 

  • 객체 중심 설계를 지원해 코드의 생산성과 유지보수성을 높임.
  • SQL 대신 메서드 호출로 데이터베이스 작업을 처리하여 간결한 코드 작성 가능.
  • 영속성 컨텍스트를 통해 1차 캐시, 지연 로딩 등 최적화된 데이터 관리 제공.
  • JPQL로 객체 중심의 쿼리 작성 가능.
  • 다양한 데이터베이스와 독립적인 표준 API로 확장성과 호환성 보장.
  • 자동화된 데이터베이스 매핑으로 개발 시간 단축.
  • 변경 감지 및 트랜잭션 관리 기능으로 안정적인 데이터 처리가능.

 

JPA가 어노테이션인 이유

JPA에서 어노테이션을 사용하는 이유는 객체-관계 매핑(ORM, Object-Relational Mapping)을 간편하고 선언적으로 처리하기 위함입니다. 즉, 객체 지향적인 코드에서 관계형 데이터베이스의 데이터를 관리할 때 발생하는 여러 번거로운 작업들을 쉽게 처리할 수 있도록 도와줍니다. 여기서 중요한 점은 JPA가 객체와 데이터베이스 테이블 간의 매핑을 어노테이션으로 지정함으로써 코드가 더 간결하고 유지보수가 용이해진다는 점이다.

 

테스트 환경 생성

  • 단순한 코드의 경우 그냥 수정할 수 있지만, 복잡한 코드가 될 수록 수정에 신중해야한다. 그래서 test코드를 작성하여 컴파일 해보면서 문제가 없는지, 버그는 없는지, 제대로 동작하는지 테스트 하는 것이 중요하다.
  • 문론 테스트 코드도 유지보수를 해야하기 때문에 의미있는 테스트 케이스를 작성해야한다.
  • 서버에 올리는 코드는 에러가 없어야한다. 즉, 이 과정은 정말 중요한 과정이다.

 

@Query와 Querydsl

  • JPA에서 SQL 쿼리를 작성하는 2가지 대표적인 방법이다.
  • 쿼리는 데이터베이스에서 원하는 데이터를 조회하거나 조작하기 위한 명령어를 말합니다.

1. @Query

@Query는 JPA 리포지토리에서 제공하는 어노테이션을 사용하여 JPQL(Java Persistence Query Language) 또는 네이티브 SQL 쿼리를 작성할 수 있게 해줍니다.

장점

  • 간단하고 직관적: SQL이나 JPQL을 직접 작성하기 때문에, 쿼리가 간단하고 직관적입니다. SQL에 익숙한 개발자라면 바로 사용할 수 있습니다.
  • 구현이 쉬움: 쿼리를 메소드에 어노테이션으로 추가할 수 있어서 코드 작성이 간단하고 빠릅니다.
  • 복잡한 쿼리도 가능: JPQL이나 네이티브 SQL로 복잡한 쿼리를 작성할 수 있기 때문에, SQL에 대한 제어가 가능합니다.
  • 쿼리 작성 시 강력한 최적화 가능: SQL을 직접 작성할 수 있어 데이터베이스에 최적화된 쿼리를 작성할 수 있습니다.

단점

  • 쿼리 재사용이 어려움: 쿼리를 여러 곳에서 사용할 경우, 쿼리가 중복될 수 있어 유지보수가 어렵습니다.
  • 쿼리 복잡도 증가: 복잡한 동적 쿼리를 작성할 때, 여러 조건을 동적으로 처리하는 것이 번거로울 수 있습니다.
  • 타입 안정성 부족: 문자열로 쿼리를 작성하기 때문에 컴파일 타임에 쿼리가 잘못 작성되었는지 확인할 수 없습니다. 즉, 쿼리 문법이나 컬럼명이 잘못된 경우 런타임에 오류가 발생할 수 있습니다.
  • 컴파일 시점에서 에러가 안나온다.

2. Querydsl

Querydsl은 자바 코드로 SQL 쿼리를 작성할 수 있게 해주는 라이브러리입니다. 타입 안전성을 제공하며, 쿼리를 빌더 방식으로 동적으로 생성할 수 있습니다.

장점

  • 타입 안전성: 컴파일 타임에 쿼리를 검증할 수 있어, 잘못된 필드명이나 잘못된 쿼리 문법에 대해 오류를 미리 확인할 수 있습니다.
  • 동적 쿼리 작성 용이: 여러 조건을 기반으로 동적으로 쿼리를 생성할 수 있어 복잡한 쿼리를 유연하게 작성할 수 있습니다.
  • 쿼리 재사용 용이: Querydsl을 사용하면 쿼리를 객체 지향적으로 작성할 수 있어, 코드 재사용과 유지보수가 용이합니다.
  • 자동 완성 지원: IDE에서 자동 완성 기능을 제공하므로, 쿼리 작성 시 실수를 줄일 수 있습니다.

단점

  • 설정과 초기 학습 곡선: Querydsl을 프로젝트에 통합하는 데 다소 시간이 걸리며, 초기 설정이 복잡할 수 있습니다.
  • 추가 의존성: Querydsl을 사용하려면 추가 라이브러리나 빌드 설정이 필요하므로 프로젝트에 의존성이 늘어날 수 있습니다.
  • 가독성 저하: 자바 코드로 SQL을 작성하기 때문에, 단순한 쿼리에서는 오히려 가독성이 떨어질 수 있습니다. 복잡한 쿼리를 작성할 때 코드가 길어질 수 있습니다.

결론

  • @Query단순하고 빠른 개발을 위해 좋습니다. 작은 프로젝트나 복잡하지 않은 쿼리에서는 매우 유용하며, 기존 SQL 문법에 익숙한 개발자에게 적합합니다.
  • Querydsl동적이고 복잡한 쿼리를 작성할 때 유리합니다. 타입 안전성과 재사용성을 제공하므로, 대규모 프로젝트나 복잡한 비즈니스 로직을 구현할 때 더 효과적입니다.

 

Thymeleaf를 사용한 이유

 

+ Thymeleaf는 Java 기반의 서버 사이드 템플릿 엔진으로, 주로 Spring Framework와 함께 사용됩니다. HTML, XML, JavaScript, CSS 등의 파일을 동적으로 렌더링 (데이터를 가공)할 수 있는 템플릿 시스템입니다.

= 컨트롤러에서 데이터 처리 -> 타임리프가 템플릿화 -> 스프링이 데이터 전송

 

1. 자연스러운 템플릿(Natural Templates):

  • 타임리프: HTML 파일을 템플릿 엔진 없이 열어도 유효한 HTML로 보이고, 동적 데이터가 서버에서 렌더링됩니다. 디자이너와 개발자가 협업하기 용이합니다.
  • 다른 엔진 (예: Freemarker, Velocity): HTML 구조와 템플릿 문법이 혼합되어 있기 때문에, 템플릿 파일을 직접 브라우저에서 열면 정상적인 HTML로 보이지 않습니다. 또한, HTML 파일을 비전문가와 협업하기 어려울 수 있습니다.

2. 강력한 표현식 언어:

  • 타임리프: 조건문, 반복문, 텍스트 출력 등 다양한 표현식 기능을 제공하며, HTML 내에서 쉽게 사용할 수 있습니다. 특히, 속성 값을 처리할 때 유연하고 직관적입니다.
  • 다른 엔진: 다른 템플릿 엔진들도 조건문, 반복문 등을 제공하지만, 타임리프처럼 HTML 속성 내에서 직접 사용할 수 있는 방식은 아닙니다. 예를 들어, Freemarker는 <#if>나 <#list>와 같은 태그를 사용해야 하며, Velocity는 $!{}와 같은 특수 문법을 사용합니다. 타임리프는 이러한 문법을 덜 사용하고, HTML 내에서 자연스럽게 사용할 수 있습니다.

3. Spring과의 통합성:

  • 타임리프: Spring과 매우 잘 통합되며, Spring Boot에서 기본적으로 지원됩니다. Spring의 다양한 기능(모델 객체, 메시지 처리, 국제화 등)과 자연스럽게 결합할 수 있습니다.
  • 다른 엔진: FreemarkerVelocity도 Spring과 통합할 수 있지만, 타임리프만큼 자연스럽고 쉽지는 않습니다. 특히 Spring Boot와의 통합에 있어 타임리프는 기본 지원이므로 설정이 간단합니다.

4. 확장 가능성:

  • 타임리프: 커스텀 디얼리티브나 프로세서를 만들어 확장이 가능하며, 새로운 태그나 속성을 쉽게 추가할 수 있습니다.
  • 다른 엔진: FreemarkerVelocity는 확장성도 있지만, 타임리프는 확장이 용이하고, 사용자가 직접 커스텀 태그를 추가할 때 좀 더 직관적이고 깔끔한 방식으로 구현할 수 있습니다.

5. 강력한 디버깅 기능:

  • 타임리프: 템플릿 렌더링 시 오류 메시지를 상세히 제공하여 디버깅을 용이하게 합니다. 오류를 명확하게 알려줘서 개발자가 쉽게 문제를 찾을 수 있습니다.
  • 다른 엔진: FreemarkerVelocity는 디버깅 기능이 있지만, 타임리프처럼 직관적이고 명확한 오류 메시지를 제공하는 데는 부족할 수 있습니다.

6. 모듈화된 레이아웃 지원:

  • 타임리프: 레이아웃을 모듈화할 수 있는 기능(th:replace, th:include)을 제공하여 코드 중복을 줄이고 유지보수성을 높입니다.
  • 다른 엔진: FreemarkerVelocity도 레이아웃을 모듈화할 수 있는 기능을 제공하지만, 타임리프의 th:replace, th:include 같은 방식은 더 직관적이고, HTML 속성으로 처리할 수 있어 코드 작성이 용이합니다.

7. 보안:

  • 타임리프: 기본적으로 HTML 이스케이프 기능이 제공되어 XSS(교차 사이트 스크립팅)와 같은 보안 문제를 예방할 수 있습니다.
  • 다른 엔진: FreemarkerVelocity도 보안 기능을 제공하지만, 기본적으로 HTML 이스케이프 처리가 되지 않는 경우가 많습니다. 보안을 강화하려면 개발자가 별도로 처리해야 할 수 있습니다.

 

DTO 클래스

  • Data Transfer Object의 약자로, 엔티티 객체를 그대로 사용하지 않고 데이터를 포장하여 원하는 값만 전달하기 위해 필요한 데이터 전송 담당 클래스
  • View Layer와 DB Layer의 분리를 위해서, 엔티티 객체의 변경을 막기 위해서, 데이터베이스 설계 외부 노출을 방지하기 위해 등 보안과 효율적인 면을 위해서 사용하게 된다.

 

부트스트랩

  • HTML, CSS, JavaScript로 구성된 오픈 소스 프론트엔드 프레임워크로, 웹 페이지나 웹 애플리케이션을 보다 빠르고 효율적으로 개발할 수 있도록 돕는 도구
  • 타인이 만든 웹 디자인을 공유할 수 있는 페이지
  • 백엔드는 프론트를 하는 경우도 많지만 일단은 웹 디자이너 역활은 못하기에... 이걸로 대체했다..

 

스프링 시큐리티

  • 단순히 로그인 보안에 사용했다.
  • 다만 여러 기능이 있는 만큼, 더 갈고 닦을 필요가 있어보인다.
  • 내용은 단순히 검문 시스템 정도로 이해하면 편한 것 같다.
  • 요청이 들어오면 요청에 대한 검사와 처리를 수행하는 메서드로 구성된다.
  • 관리자와 비 관리자의 보안 관리는 다르게 들어가는데, 가장 큰 이유는 여기서 데이터 정보를 어디까지 허용할지 지정하기 때문

 

매핑

  • 클라 - 서버 - DB는 서로 데이터를 보내거나 불러올때 계속 매핑을 해야한다.
  • 간단히 서로 언어가 다르니 번역해야한다는 것, [ 사과 - 애플 - 링고 ] 이런식으로 언어가 다르다 보니 서로 번역하는 작업이 필요한데 이게 매핑이다.
  • 매핑은 처음에는 언제 사용해야 하나 라는 생각이 들었는데, 그냥 클라, 서버, db 각각 내부에서 사용하는 변수, 상수가 아니면 싹다 매핑을 해야한다. = 서로 뭘 주고 받으면 매핑해야한다...

AJAX 요청에서 401 Unauthorized 상태 코드를 반환하는 이유

일반적으로 AJAX 요청은 페이지 리로드 없이 서버와 비동기적으로 데이터를 주고받기 위해 사용되기 때문(웹 페이지 일부만 업데이트해서 리로드 없어도 가능함)입니다. 즉, 브라우저가 페이지를 새로고침하지 않고도 서버와 데이터를 주고받을 수 있는 방식입니다. 이때, 인증되지 않은 상태일 경우 어떻게 처리할지에 대한 특별한 요구사항이 있을 수 있습니다.

 

이유:

  1. 페이지 리디렉션이 불가능하기 때문에:
    • 일반적인 HTTP 요청에서는 인증이 필요할 경우, 사용자를 로그인 페이지로 리디렉션할 수 있습니다.
    • 하지만 AJAX 요청은 페이지 리로드를 하지 않고 데이터를 주고받는 방식이기 때문에, 리디렉션을 처리할 수 없습니다.
    • 만약 HTTP 401 Unauthorized 상태 코드 대신 로그인 페이지로 리디렉션하면, AJAX 요청을 보낸 클라이언트는 리디렉션을 처리하지 못하고, 예상하지 못한 동작이 발생할 수 있습니다. 예를 들어, 클라이언트는 로그인 페이지로 리디렉션된 후, 이를 다시 처리하기 위해 페이지를 새로고침해야 할 수 있습니다.
  2. AJAX 응답의 형식:
    • AJAX 요청은 비동기적으로 서버와 통신하므로, 클라이언트는 서버로부터 데이터를 받아와서 페이지 내에서 처리합니다.
    • 서버에서 401 Unauthorized 상태 코드를 반환하면, 클라이언트 측에서 이를 처리하고, 사용자에게 적절한 오류 메시지를 표시하는 등 후속 작업을 할 수 있습니다.
    • 리디렉션을 대신하여, 클라이언트는 이 상태 코드를 처리하여 사용자를 로그인 페이지로 유도할 수 있습니다. 예를 들어, alert()으로 "로그인 필요" 메시지를 띄우거나, 로그인 페이지로 리디렉션하는 방식으로 처리할 수 있습니다.

+ 리디렉션(Redirection)은 웹에서 사용자가 특정 요청을 했을 때, 서버가 다른 URL로 사용자를 자동으로 보내는 방법을 의미합니다.

 

연관 관계 매핑 종류

  • 매핑이 당연히 1:1이라 생각할 수 있지만, 아래처럼 일대다의 형태를 띄기도 한다. 이럴경우 리스트로 받아온다.
  • 이 연관 관계 매핑을 잘 생각하고 매핑해야한다. 정보 이상하게 불러오면 처리고 뭐고 다 망해간다.

 

1. 단방향 관계 (Unidirectional)

  • 한 엔티티에서 다른 엔티티를 참조하는 형태입니다.
  • 다른 방향으로의 접근은 불가능하고, 하나의 엔티티에서만 연관된 엔티티에 접근할 수 있습니다.

2. 양방향 관계 (Bidirectional)

  • 두 엔티티가 서로를 참조하는 형태입니다.
  • 두 방향으로의 접근이 가능하며, 이를 설정하기 위해 mappedBy를 사용하여 연관 관계를 설정합니다.

 

연관 관계 매핑 종류

1. One-to-One (1:1 관계)

  • 한 엔티티가 다른 엔티티와 1:1 관계를 맺는 경우입니다.
  • 예시: 한 사람은 하나의 여권만 가질 수 있다.

2. One-to-Many (1:N 관계)

  • 한 엔티티가 여러 엔티티를 참조하는 경우입니다.
  • 예시: 하나의 게시글에는 여러 댓글이 있을 수 있다.

3. Many-to-One (N:1 관계)

  • 여러 엔티티가 한 엔티티를 참조하는 경우입니다.
  • 예시: 여러 댓글은 하나의 게시글에 속한다.

4. Many-to-Many (N:M 관계)

  • 여러 엔티티가 서로 다대다 관계를 가지는 경우입니다.
  • 예시: 여러 학생은 여러 과목을 수강할 수 있다.

 

영속성 전이

  • 엔티티의 상태를 변경할 때 해당 엔티티와 연관된 엔티티의 상태 변화를 전파시키는 옵션을 말한다.
  • 이때 부모는 One에 해당하고 자식은 Many에 해당한다.
  • 예시로 Order엔티티 삭제 - 연관된 다른 엔티티 삭제 / Order엔티티를 저장할때 포함된 다른 엔티티도 저장되는 경우
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)

 

 

고아 객체의 삭제

부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 말하는 것으로, 영속성 전이 기능과 같이 사용하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.

  • 다만 고아 객체는 위의 영속성 전이와 같이 다른 곳에 영향을 줄 수 있다는 점에서 삭제에 유의해야한다.
  • 고아 객체가 하나의 참조만 하고 있다면 삭제가 가능하지만, 그게 아니라면 어떤 변수가 발생할지 모르기에 삭제가 불가능 하다.
  • 예시 : orphanRemoval = true라고 하면 만약 주문 항목(부모) 삭제시, 항목들도 데이터 베이스에서 삭제된다.

 

지연 로딩

일반적으로 로딩을 시전하면 DB 특정 항목의 모든 데이터를 불러온다. 이러한 대참사를 막기 위해 있는 것이 지연 로딩 방식이다. 지연 로딩은 데이터를 실제로 필요할 때까지 로딩을 지연시키는 방식이다. 즉 관련된 엔티티나 컬렉션이 실제로 사용되기 전까지는 DB에서 불러오지 않는다는 것.

 

Auditing을 이용한 엔티티 공통 속성 공통화

Auditing = Spring Data Jpa에서 제공하는 엔티티가 저장 or 수정될 때 자동으로 등록일,수정일,등록자,수정자를 입력해주는 기능을 말한다. 실제 영어 뜻도 '감시하다'의 의미를 가지고 있다. 

  • 문론 위의 정보 중 필요한 정보만 자동 저장할 수 있게 만들 수 있다.

 

애플리케이션 실행 상태에서 테이블 수정

일반적으로 소스를 작성하면서 애프릴케이션을 재실행하면 테이블을 삭제하고 다시 만들기 때문에 코드를 실행한 이후에 저장된 데이터는 삭제된다. 이 과정을 해결하기 위해 application.prperties의 dll-auto 속성을 validate로 변경하면 애플리케이션 실행 시점에 테이블을 삭제한 후 재생성하지 않으면 엔티티와 테이블이 매핑이 정상적으로 되어 있다.

= 디버깅 중에 테이블 내용을 수정할 수 있다.

 

Hibernate의 ddl-auto 속성

hibernate.hbm2ddl.auto는 Spring Data JPA에서 데이터베이스 테이블을 자동으로 관리하는 방법을 지정하는 설정입니다. 애플리케이션을 실행할 때, 이 설정에 따라 데이터베이스의 테이블이 어떻게 처리될지 결정됩니다.

여기서 validate, update, create, create-drop 값들이 중요한데, 각각 어떤 동작을 하는지에 대해 간단히 설명해볼게요.

문제의 핵심:

  1. 테이블 삭제 후 재생성:
    • 기본적으로 **create**나 **create-drop**을 설정하면, 애플리케이션이 시작될 때 데이터베이스의 테이블이 삭제되고 새로 생성됩니다. 이때 기존에 저장된 데이터는 모두 삭제됩니다.
  2. validate로 설정하면?:
    • hibernate.hbm2ddl.auto=validate로 설정하면, 애플리케이션이 시작될 때 기존 테이블을 삭제하거나 새로 만들지 않습니다.
    • 대신, 데이터베이스의 테이블 구조가 애플리케이션의 엔티티 클래스와 잘 매핑되어 있는지 확인합니다. 만약 테이블 구조가 엔티티 클래스와 일치하지 않으면 애플리케이션이 예외를 발생시키며 실행되지 않습니다.
    • 즉, 엔티티와 데이터베이스 테이블이 잘 매핑되어 있다면 테이블은 변경되지 않고, 데이터도 유지됩니다.
spring.jpa.hibernate.ddl-auto= create

-----------------------------------------

spring.jpa.hibernate.ddl-auto= validate

 

화면이 너무 복잡하고 얽힌 데이터가 많으면

Vue.js 프레임워크를 사용해보자

  • 데이터가 변하면 해당 데이터를 보여주는 영역에 뷰도 자동으로 바뀌고, 데이터가 변하는 걸 감시하고 있다가 이벤트를 발생하기 쉽다.
  • Vue.js는 반응형(Reactivity) 시스템을 제공합니다. 이 시스템 덕분에 데이터가 변경되면 해당 데이터를 사용하는 UI(뷰)가 자동으로 업데이트됩니다.
  • 를 들어, 사용자가 데이터를 수정하거나 서버에서 데이터를 받아오면 Vue.js는 이 데이터를 감시하고 있다가, 데이터가 변경될 때마다 관련된 UI 요소를 자동으로 갱신합니다.
  • 이는 상태 관리UI 갱신을 자동으로 해주기 때문에, 개발자는 UI를 직접 수정할 필요 없이 데이터만 수정하면 화면이 자동으로 반영됩니다.
  • 그러니까 변형의 자동 인지와, 관련된 UI를 자동으로 업데이트 해준다는 것

 

 

네트워크 생명주기 모델

 

1. 네트워크 생명주기 (요청-응답 흐름)

위 코드가 작동하는 전체 네트워크 생명주기는 아래와 같이 설명됩니다.

① 요청 (Request)

  1. 클라이언트(브라우저 또는 JavaScript)는 서버로 요청을 보냅니다.
    • URL: /order (POST 요청)
    • 데이터: JSON ({ "itemId": 1, "count": 2 })
    • 방식: AJAX를 통해 비동기적으로 전달.

② 처리 (Processing)

  1. 컨트롤러 (OrderController)
    • @PostMapping("/order") 메서드가 요청을 받습니다.
    • @RequestBody를 통해 JSON 데이터를 Java 객체(OrderDto)로 매핑.
    • 유효성 검사 결과(BindingResult)를 확인하고, 서비스 계층 호출.
  2. 서비스 (OrderService)
    • 비즈니스 로직을 처리하며, 데이터베이스 접근 및 주문 처리.
    • 처리 결과로 주문 ID를 반환.

③ 응답 (Response)

  1. 컨트롤러가 처리 결과를 JSON 형태로 반환:
    • ResponseEntity<Long>로 반환된 주문 ID를 JSON으로 변환.
    • 클라이언트는 AJAX의 성공 콜백 함수에서 이를 처리.

④ 클라이언트 반응

  1. 클라이언트는 서버에서 받은 JSON 데이터를 활용하여 화면 업데이트:
    • 주문 완료 메시지 표시.
    • 주문 ID와 관련된 정보를 화면에 출력.

2. 요청-응답 간 상호작용 요약

  • 클라이언트 → 서버:
    AJAX로 JSON 데이터를 서버에 전달 (@RequestBody).
  • 서버 → 클라이언트:
    처리 결과를 JSON 형태로 반환 (@ResponseBody).

3. AJAX 사용의 장점

  • 페이지 새로고침 없이 실시간 업데이트 가능.
  • 사용자 경험(UX)을 크게 개선.
  • 요청-응답 간 데이터 크기를 줄여 효율성 증가.

4. 코드 흐름에 AJAX가 통합되는 구조

  1. 프론트엔드에서 주문 요청 시 AJAX를 호출.
  2. 백엔드 컨트롤러와 서비스 계층이 데이터 처리.
  3. 응답 데이터를 클라이언트가 받아 사용자 인터페이스(UI) 업데이트.

+ json = 클라이언트, 서버 통신시 사용되는 경량형 데이터 형식 (map 구조)

+ AJAX = 클라이언트 ,서버 통신시 비동기 통신을 가능하게 해주는 웹 기술 (프로토콜 아님)

 

프론트와의 통신

 

  • 맵핑은 기본적으로 서버에서 진행한 다음 db나 클라에게 보낸다.
  • $(".custom-file-input"):
    • 의미: 이 코드는 jQuery를 사용하여 클래스 이름이 custom-file-input인 HTML 요소들을 선택하는 코드입니다.
    • $(".custom-file-input")는 HTML 문서 내에서 클래스가 custom-file-input인 모든 요소를 찾아 조작하거나 스타일을 적용할 수 있게 합니다.
    • 예를 들어, 파일 선택 버튼을 커스터마이즈할 때 자주 사용됩니다.
  • $("#searchBtn"):
    • 의미: 이 코드는 jQuery를 사용하여 아이디가 searchBtn인 HTML 요소를 선택하는 코드입니다.
    • $("#searchBtn")는 id="searchBtn"인 요소를 선택하여 해당 요소를 조작할 수 있습니다. 일반적으로 버튼 클릭 이벤트를 다룰 때 사용됩니다.
    • 예를 들어, 검색 버튼을 클릭했을 때 검색 기능을 활성화하는 용도로 사용될 수 있습니다.
  • th:href="@{/css/layout1.css}":
    • 의미: 이 코드는 Thymeleaf 템플릿 엔진을 사용하여 HTML 링크 태그의 href 속성을 동적으로 설정하는 코드입니다.
    • th:href는 Thymeleaf의 특수 속성으로, 페이지가 렌더링될 때 href 속성을 해당 URL로 변환합니다. @{}는 해당 URL을 프로젝트의 루트 경로를 기준으로 계산해줍니다.
    • 예를 들어, @{/css/layout1.css}는 /css/layout1.css라는 경로로 CSS 파일을 참조하는 링크를 만듭니다. 이 경로는 애플리케이션의 컨텍스트 경로를 자동으로 고려하여 적절하게 해석됩니다.
  • th:
    • 의미: th는 Thymeleaf 템플릿 엔진에서 사용하는 네임스페이스의 접두어입니다. Thymeleaf는 HTML을 렌더링할 때 동적 데이터를 삽입하거나 처리할 수 있게 해주는 서버 사이드 템플릿 엔진입니다.
    • th 접두어는 Thymeleaf 특수 속성을 나타내며, 예를 들어 th:href, th:text, th:src와 같은 속성을 사용할 수 있습니다. 이는 HTML 속성에 동적 값을 삽입하거나 변환할 때 사용됩니다.
    예시:
    • th:text="${message}": message 변수에 담긴 값을 텍스트로 삽입합니다.
    • th:src="@{${imagePath}}": imagePath 변수를 경로로 변환하여 이미지 소스를 설정합니다.

 

 

 


+추가사항+

 
 

1. 트랜잭션 관리 (Transaction Management)

  • 트랜잭션은 데이터베이스에서 일련의 작업들이 일관성 있게 처리되도록 보장하는 메커니즘입니다. 예를 들어, 여러 데이터베이스 작업이 하나의 큰 작업을 구성할 때, 작업 중 하나가 실패하면 전체 작업이 롤백되어야 합니다.
  • Spring에서는 @Transactional 어노테이션을 사용하여 트랜잭션을 관리할 수 있습니다. 트랜잭션이 적용되면 메서드 실행이 끝나기 전에 모든 DB 작업이 성공적으로 처리되거나 롤백됩니다.
  • 트랜잭션 전파, 격리 수준, 예외 처리 등도 고려해야 할 중요한 부분입니다.

2. 캐시 (Cache)

  • 캐싱은 애플리케이션 성능을 최적화하는 중요한 기법입니다. 자주 조회되는 데이터를 메모리에 저장해두고, 데이터베이스 접근을 최소화하는 방법입니다.
  • Spring에서는 @Cacheable, @CacheEvict 등의 어노테이션을 활용해 쉽게 캐시를 적용할 수 있습니다.
  • Redis나 Ehcache와 같은 외부 캐시 시스템을 연동하여 성능을 크게 향상시킬 수 있습니다.

3. API 설계 (RESTful API)

  • RESTful API는 HTTP를 통해 자원을 CRUD(Create, Read, Update, Delete) 방식으로 처리하는 API 설계 방법론입니다.
  • RESTful API 설계 시 **HTTP 메서드(GET, POST, PUT, DELETE 등)**의 사용법과 상태 코드(예: 200 OK, 404 Not Found 등)를 정확하게 이해하고 활용하는 것이 중요합니다.
  • Spring에서는 @RestController를 사용하여 REST API를 쉽게 구현할 수 있습니다.

4. 로깅 (Logging)

  • 로깅은 애플리케이션의 상태를 기록하고, 오류나 문제 발생 시 이를 추적하는 데 중요한 역할을 합니다.
  • SLF4JLogback을 사용하여 로그를 관리하고, 로그의 레벨을 적절히 설정하여 개발, 테스트, 프로덕션 환경에서 로그를 효과적으로 사용할 수 있습니다.
  • 로그 추적을 통해 성능 병목이나 오류를 빠르게 파악할 수 있습니다.

5. 보안 (Security)

  • Spring Security를 사용하여 로그인, 인증, 권한 관리 등 다양한 보안 기능을 구현할 수 있습니다.
  • CSRF, XSS, 세션 관리 등과 같은 웹 애플리케이션 보안 문제를 고려해야 합니다.
  • 인증과 권한 처리를 통해 사용자가 수행할 수 있는 작업을 제한하고, 민감한 정보에 대한 접근을 제어합니다.

6. 배포와 CI/CD (Continuous Integration / Continuous Deployment)

  • 애플리케이션을 CI/CD 파이프라인을 통해 자동으로 빌드하고 배포하는 시스템을 구축하면, 배포 시 오류를 최소화하고 효율성을 높일 수 있습니다.
  • Jenkins, GitHub Actions, GitLab CI 등을 사용하여 자동화된 빌드 및 배포 프로세스를 설정할 수 있습니다.

7. 단위 테스트(Unit Testing)

  • JUnit과 Mockito를 사용하여 단위 테스트를 작성하면, 코드 변경이 기존 기능에 영향을 미치지 않는지 확인할 수 있습니다.
  • 테스트 주도 개발(TDD, Test-Driven Development)을 통해 코드 품질을 높이고, 버그를 사전에 예방할 수 있습니다.

8. API 문서화 (API Documentation)

  • Swagger와 같은 도구를 사용하여 API 문서를 자동으로 생성할 수 있습니다.
  • Springfox 또는 Springdoc OpenAPI를 활용하여 API 문서화가 용이하며, 팀원들과의 협업 시 API 사용법을 명확하게 전달할 수 있습니다.

9. 비동기 처리 (Asynchronous Processing)

  • 비동기 작업 처리는 시간 소모적인 작업을 별도의 쓰레드에서 처리하여 애플리케이션의 응답성을 높이는 데 유용합니다.
  • Spring에서는 @Async 어노테이션을 사용하여 비동기 메서드를 처리할 수 있습니다.

10. 로그인 및 세션 관리 (Session Management)

  • 세션 관리는 사용자가 로그인 상태를 유지하도록 하는 기능입니다. JWT (JSON Web Token)와 같은 토큰 기반 인증 시스템을 활용하여 분산 환경에서도 세션을 관리할 수 있습니다.
  • 세션 스토리지와 쿠키를 잘 활용하여 사용자의 인증 상태를 안전하게 관리하는 방법을 이해해야 합니다.

'Project > Spring' 카테고리의 다른 글

Spring Querydsl 과제 회고  (1) 2025.01.27
[Spring] IOC & DI  (0) 2025.01.23
스프링 NEWSFEED 협업 프로젝트  (1) 2024.12.27
일정표를 만들어 보자! 업데이트!  (2) 2024.12.19
일정표를 만들어 보자!  (2) 2024.12.09

+ Recent posts