Post

EJB(Enterprise JavaBeans) 알아보기

개요

  • 스프링 이전의 기술들을 살펴보면 EJB에 대한 이야기가 많이 나온다.

ejb

  • 김영한 선생님 마저 눈을 감고 계시는 악명 높은 EJB가 뭔지 궁금했다.
  • 이번 기회를 통해 알아보자.


EJB의 역할

servlet+jsp

  • 지난 포스팅(JSP와 서블릿이 무엇일까?)에서 MVC 패턴 중 서블릿은 Controller를, JSP는 View를 맡았다고 했다.
  • 그렇다면 model은 누가 맡을까?
  • 서블릿(1997)과 JSP(1999)가 등장한 시기 즈음에 등장한 EJB(1998)가 자연스럽게 Model의 역할을 맡았다.


EJB가 무엇인가?

  • EJB는 자바 기반의 서버 측 컴포넌트 아키텍처로, 대규모 엔터프라이즈 애플리케이션에서 비즈니스 로직을 처리한다.
  • 대규모 엔터프라이즈 애플리케이션에서는 트랜잭션의 일관성, 데이터 무결성 등이 아주 중요하다.
  • EJB는 다음과 같은 장점으로 대규모 시스템에서 좋은 평가를 받았다.
    1. 트랜잭션을 자동으로 관리하여, 개발자가 직접 처리할 필요가 없다.
    2. 분산 트랜잭션도 지원하여 여러 시스템 간의 트랜잭션을 쉽게 처리할 수 있다.
    3. 인증/인가를 내장하여 보안이 뛰어나다.
    4. 원격 호출을 통해 분산 컴퓨팅을 지원한다.
    5. 자원 풀링, 캐싱 등으로 성능을 최적화한다.


EJB의 종류

  • EJB는 크게 3가지 유형으로 나뉜다.


[1. Session Bean]

  • 비즈니스 로직을 처리하는 데 사용된다.

    스프링에서 @Service가 붙은 클래스라고 보면 된다.

  1. Stateless Session Bean
    • 클라이언트의 상태를 유지하지 않고, 모든 요청을 독립적으로 처리한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     @Stateless
     public class CalculatorStatelessBean {
    
         // 상태 저장 x
    
         public int add(int a, int b) {
             return a + b;
         }
    
         public int subtract(int a, int b) {
             return a - b;
         }
     }
    
  2. Stateful Session Bean
    • 클라이언트의 상태 정보를 저장하여, 같은 클라이언트와 계속 상호작용한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     @Stateful
     public class ShoppingStatefulBean {
    
         private int itemCount = 0; // 상태 저장
    
         public void addItem(int count) {
             this.itemCount += count;
         }
    
         public void removeItem(int count) {
             this.itemCount -= count;
         }
    
         public int getItemCount() {
             return itemCount;
         }
     }
    
  3. Singleton Session Bean
    • 애플리케이션 내에서 단 하나의 인스턴스만 존재하며, 전역적으로 공유된다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     @Singleton
     public class CounterSingletonBean {
    
         private int count = 0; // 상태 저장 - 전역 상태나 리소스 관리에 사용된다.
    
         public void increment() {
             count++;
         }
    
         public void decrement() {
             count--;
         }
    
         public int getCount() {
             return count;
         }
     }
    


[2. Entity Bean]

  • 데이터베이스와 상호작용을 하기 위해 사용된다.

    스프링에서 @Repository가 붙은 클래스라고 보면 된다.

  • EJB 3.0 이후에는 JPA로 대체되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Stateless
public class UserServiceBean {

    @PersistenceContext
    private EntityManager entityManager;

    // 사용자 등록
    public void createUser(String name, String email) {
        User user = new User(name, email);
        entityManager.persist(user);
    }

    // ID로 사용자 조회
    public User findUserById(Long id) {
        return entityManager.find(User.class, id);
    }

    // 모든 사용자 조회
    public List<User> findAllUsers() {
        return entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
InitialContext context = new InitialContext();
UserServiceBean userService 
    = (UserServiceBean) context.lookup("java:global/myapp/UserServiceBean");

// 새 사용자 등록
userService.createUser("root", "ajroot5685@github.io");

// 사용자 조회
User user = userService.findUserById(1L);
System.out.println("이름 : " + user.getName() + ", 이메일 : " + user.getEmail());

// 모든 사용자 조회
List<User> users = userService.findAllUsers();
for (User u : users) {
    System.out.println("id : " + u.getId() + ", 이름 : " + u.getName());
}


[3. Message-Driven Bean]

  • 비동기 메세지를 처리하기 위해 사용되며, Java Message Service(JMS)와 연동하여 비동기 작업을 처리한다.

Listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@MessageDriven(activationConfig = {
        @ActivationConfigProperty(
            propertyName = "destinationType", 
            propertyValue = "javax.jms.Queue"
        ),
        @ActivationConfigProperty(
            propertyName = "destination", 
            propertyValue = "java:/jms/queue/myQueue"
        )
})
public class MessageProcessorBean implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                String text = textMessage.getText();
                System.out.println("Received message: " + text);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


Publisher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// JMS 리소스 찾기
InitialContext context = new InitialContext();
ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("java:/ConnectionFactory");
Queue queue = (Queue) context.lookup("java:/jms/queue/myQueue");

// JMS 커넥션 생성
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);

// 메시지 생성 후 큐에 전송
TextMessage message = session.createTextMessage("Hello, JMS World!");
producer.send(message);

System.out.println("메세지 발송: " + message.getText());

producer.close();
session.close();
connection.close();


EJB의 한계

  • 학습 난이도가 굉장히 높고, 자원 관리나 설정이 복잡하고 무거워 POJO(Plain Old Java Object) 또는 경량화된 Spring 프레임워크로 넘어가게 되었다.
  • 주요 단점은 다음과 같다.


  1. 클라이언트가 빈에 접근하기 위해서는 해당 빈을 생성하는 인터페이스가 추가로 필요하다.

  2. 빈은 SessionBean 인터페이스를 구현해야 하는데, EJB 라이프사이클 메서드를 명시적으로 구현해야 했다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
     public class CalculatorBean implements SessionBean {
    
         @Override
         public void ejbCreate() {} // 빈 생성 시 처리
    
         @Override
         public int add(int a, int b) {
             return a + b;
         }
    
         @Override
         public int subtract(int a, int b) {
             return a - b;
         }
    
         @Override
         public void setSessionContext(SessionContext context) {} // 세션 컨텍스트 설정
    
         @Override
         public void ejbRemove() {} // 빈 제거 처리
    
         @Override
         public void ejbActivate() {} // 활성화 처리
    
         @Override
         public void ejbPassivate() {} // 비활성화 처리
     }
    
  3. XML 설정이 복잡했다. 이는 초기 스프링도 마찬가지로 넘어가겠다.

  4. JNDI를 통한 접근

    • EJB 2.x에서는 클라이언트가 빈에 접근하기 위해 JNDI를 사용해야 했는데 매번 처리하는 것이 번거롭고 코드가 지저분해졌다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     public class EJBClient {
         public static void main(String[] args) throws Exception {
             Context context = new InitialContext();
             CalculatorLocalHome home 
                 = (CalculatorLocalHome) context.lookup("java:comp/env/ejb/CalculatorBean");
             CalculatorLocal calculator = home.create();
    
             int sum = calculator.add(10, 5);
             System.out.println("합계 : " + sum);
         }
     }
    

분산 처리 vs MSA(Micro Service Architecture)

  • 요즘 대규모 시스템 하면 MSA로 구성된 시스템을 떠올리기 쉽다.
  • 그럼 EJB가 대규모 시스템에 적합하다고 했는데, MSA에 적합할까? 결론부터 말하자면 전혀 다르다.
요소EJB(분산 컴퓨팅)MSA
아키텍처 구조모놀리식 구조에서
비즈니스 로직의 중앙 집중화
독립된 마이크로서비스들의
느슨한 결합 구조
통신 방식RMI, JNDI 기반의 동기적 통신REST, gRPC, 메세지 큐 등의
다양한 방식의 통신
유연성 및 독립성중앙화된 구조로 독립성 제한각 서비스의 독립성과 자율성 보장
  • MSA에서는 독립적인 컴포넌트들이 상호작용하는 것이므로, 하나의 컴포넌트에 트래픽이 몰리면 해당 컴포넌트만 확장시켜주면 된다.
  • 그러나 EJB는 중앙 집중식 구조이기 때문에 모든 요청이 몰리므로 부하가 발생한다.

[Remote Method Invocation]

  • 원격 시스템에서 실행 중인 자바 객체의 메서드를 로컬에서 호출하듯 호출하게 해준다.
  • 클라이언트는 위에서 JNDI를 통해 로컬 빈 객체를 접근하듯이 호출하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RMIServer {
    public static void main(String[] args) {
        try {
            // 원격 객체 생성
            Calculator calculator = new CalculatorImpl();
            
            // RMI Registry 생성 및 원격 객체 바인딩
            Registry registry = LocateRegistry.createRegistry(1099);  // 기본 포트: 1099
            registry.bind("CalculatorService", calculator);

            System.out.println("Calculator RMI Server is running...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RMIClient {
    public static void main(String[] args) {
        try {
            // RMI Registry에서 원격 객체 찾기
            // localhost 자리에는 다른 서버 IP나 도메인 주소가 들어갈 수 있다.
            Registry registry = LocateRegistry.getRegistry("localhost", 1099);
            Calculator calculator = (Calculator) registry.lookup("CalculatorService");

            // 원격 메서드 호출 - 로컬 메서드를 호출하는 것과 형식이 같다.
            int sum = calculator.add(5, 3);
            System.out.println("Sum: " + sum);

            int diff = calculator.subtract(10, 4);
            System.out.println("Difference: " + diff);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

[그림으로 알아보기]

distributed system

  • RMI, JNDI와 같은 기술을 통해 원격 메서드를 쉽게 사용할 수 있다.
  • 이는 중앙 서버에서 실제 비즈니스 작업을 다른 원격 서버에 위임시킬 수 있다는 뜻이 되고 결과적으로 중앙 서버의 부하가 줄어드는 효과를 볼 수 있다.
  • 즉, 모든 요청은 중앙 서버를 통과하지만 실제 비즈니스 작업은 다른 서버가 수행하게 된다.


msa

  • 반면 MSA에서는 요청이 하나의 서버에 집중되지 않고, 독립된 여러 서버로 나뉘어진다.
  • 때문에 EJB의 분산 처리와 MSA는 구조적으로 다르다.
This post is licensed under CC BY 4.0 by the author.