본문 바로가기

Java/Vert.x

버텍스 코어: 버티클 팩토리(Verticle Factory)

이 문서의 내용

    더보기

    버티클 팩토리는 Vertx 인스턴스가 버티클을 배포 하는 과정에서 인자로 받는 버티클 이름을 버티클 인스턴스로 맵핑합니다.

    이는 여러 언어(Polyglot)로 작성된 버티클을 하나의 애플리케이션에서 배포하기 위함입니다.

    예를 들어, Java, Javascript 및 Kotlin 등으로 작성된 버티클 소스 파일을 Java 애플리케이션이 일괄 배포하게 할 수 있습니다.

    접두사(:)

    버티클의 이름은 접두사와 접미사로 구분 될 수 있습니다.

    접두사는 클론(:)으로 구분 되는 가장 첫 문자열입니다.

    아래 코드는 VerticleFactory에서 제공하는 정적 메소드로써, 문자열(버티클 이름)에서 접두사를 제거하여 리턴합니다.

    static String removePrefix(String identifer) {
    	int pos = identifer.indexOf(':');
    	if (pos != -1) {
    		if (pos == identifer.length() - 1) {
    			throw new IllegalArgumentException("Invalid identifier: " + identifer);
    		}
    		return identifer.substring(pos + 1);
    	} else {
    		return identifer;
    	}
    }

    접두사와 접미사는 버티클 이름을 버티클 팩토리에 맵핑하기 위한 가장 중요한 요소입니다.

    버티클이 어떤 언어로 작성되었는지를 특정할 수 있기 때문입니다.

    • 자바 스크립트는 js: 접두사를 사용합니다.
    • 그루비는 service: 접두사를 사용합니다.
    vertx.deployVerticle("js:some-js-verticle");
    vertx.deployVerticle("service:some-js-verticle");

    접미사(.)

    접미사는 어떤 언어로 작성되었는지에 대한 힌트입니다.

    일반적으로 특정 언어로 작성된 파일 확장자와 일치하며 도트(.)로 구분 되는 가장 마지막 문자열입니다.

    vertx.deployVerticle("some-js-verticle.js");
    vertx.deployVerticle("some-js-verticle.groovy");

    자바 정규 클래스 이름(FQCN, Fully Qualified Class Name)

    자바에서는 클래스 이름을 표시 할 때 패키지 경로 . 클래스 이름을 사용합니다.

    이러한 표현 방법은 패키지부터 시작해서 클래스, 함수, 변수 등의 계층적 구조를 내포합니다.

    버티클을 배포 할 때 접두사와 접미사를 포함하지 않으면 디폴트로 FQCN 규칙을 따릅니다.

    // FQCN을 직접 입력한 경우
    vertx.deployVerticle("io.vertx.some.package.some.class.name");
    
    // FQCN에 따라서 자동 처리되는 경우
    vertx.deployVerticle(SomeClassName.class.getName());
    
    // getSimpleName() 메소드는 클래스 이름만 포함하므로 오류 발생!
    vertx.deployVerticle(SomeClassName.class.getSimpleName());

    버티클 팩토리 구현

    임의의 규칙을 적용해서 새로운 버티클 팩토리를 구현 할 수 있습니다.

    우선 버티클 팩토리(io.vertx.core.spi.VerticleFactory) 인터페이스를 구현하는 클래스를 작성하고 필수 메소드에 해당하는 prefix()createVerticle()를 오버라이드합니다.

    public static class MyVerticleFactory implements VerticleFactory 
    {
    	@Override
    	public String prefix() { return null; }
        
    	@Override
    	public void createVerticle(String verticleName, ClassLoader classLoader, Promise<Callable<Verticle>> promise) { }
    }

    새로 작성한 버티클 팩토리를 인스턴스로 생성하여 vertx 인스턴스에 등록합니다.

    vertx.registerVerticleFactory(new MyVerticleFactory());

    버티클 팩토리: prefix()

    접두사 문자열을 반환하는 메소드입니다.

    접두사 문자열에는 클론(:)을 포함하지 않으며, 버티클 이름을 버티클 팩토리에 매핑해주는 가장 중요한 키입니다.

    @Override
    public String prefix() { return "my"; }

    버티클 팩토리: createVerticle()

    매핑이 완료된 이후, 버티클 인스턴스를 생성하여 핸들러에 반환해야 합니다.

    메소드가 버티클 인스턴스를 생성하기 위한 힌트로 버티클 이름(verticleName) 인자를 제공 받습니다.

    인자로는 접두사를 포함한 전체 문자열이 전달되므로 불필요한 요소를 제거해야 합니다. 

    String className = VerticleFactory.removePrefix(verticleName);

    접두사를 제거하고 FQCN에 따른 클래스 이름을 구하였습니다.

    다음은 두 번째 인자로 받는 클래스 로더(ClsasLoader)를 사용하여 클래스 이름을 통해 해당 버티클 인스턴스를 생성합니다.

    클래스를 로드(loadClass)하고 인스턴스화(newInstance)하는 과정을 구현합니다.

    Class<? extends Verticle> clazz = (Class<? extends Verticle>) classLoader.loadClass(className);
    Verticle v = clazz.newInstance();

    이렇게 생성된 버티클 인스턴스는 핸들러로 Callable 리턴됩니다.

    promise.complete(() -> v);

    버티클 팩토리 전체 구현 코드 샘플

    여기까지가 버티클 팩토리를 직접 구현하는 과정입니다.

    사실 지금까지 구현한 과정이 버텍스가 제공하는 자바의 버티클 팩토리와 크게 다름이 없습니다.

    약간의 차이점이라고 하면 접두사(my:)를 사용하여 버티클을 배포하면 FQCN으로 동작하는 버티클 팩토리를 직접 구현하였다는 점입니다.

    public static class MyVerticleFactory implements VerticleFactory 
    {
    	@Override
    	public String prefix() { return "my"; }
    
    	@Override
    	public void createVerticle(String verticleName, ClassLoader classLoader, Promise<Callable<Verticle>> promise) 
    	{
    		try {
    			String className = VerticleFactory.removePrefix(verticleName);
    			Class<? extends Verticle> clazz = (Class<? extends Verticle>) classLoader.loadClass(className);
    			Verticle v = clazz.newInstance();
    			promise.complete(() -> v);
    		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
    			promise.fail(e);
    		}
    	}
    }

    마지막으로 버티클 팩토리는 vertx 인스턴스에 등록합니다.

    이제부터는 우리가 정의한 패턴에 따라서 버티클 팩토리에 자동으로 맵핑됩니다.

    vertx.registerVerticleFactory(new MyVerticleFactory());
    vertx.deployVerticle("my:vertx.MyVerticle");
    더보기

    입력한 문자열이 두 개 이상의 버티클 팩토리가 동시에 매핑되는 경우도 있을 수 있습니다.

    이 경우 어떤 버티클 팩토리를 결정해야 하는지 판단 기준이 필요합니다.

    우선 순위는 버티클 팩토리가 구현하는 order() 메소드의 리턴 정수를 오름차순한 결과입니다(즉, 리턴 값이 가장 작은 것이 우선 결정됩니다).

    이 메소드는 오버라이드하지 않으면 0을 리턴합니다.

    default int order() { return 0; }

    아래 예시는 버티클 팩토리를 두 개 구현하고, 서로 같은 매핑 패턴을 갖도록 합니다.

    • MyVerticleFactory는 order() 메소드에서 12345를 리턴합니다.
    • MyVerticleFactory2는 order() 메소드에서 1을 리턴합니다.

    따라서 동시 매핑에 대한 우선 순위로 MyVerticleFactory2가 우선 선정됩니다.

    public static class MyVerticleFactory implements VerticleFactory 
    {
    	@Override
    	public String prefix() { return "my"; }
    
    	@Override
    	public int order() { return 12345; }
    
    	@Override
    	public void createVerticle(String verticleName, ClassLoader classLoader, Promise<Callable<Verticle>> promise) 
        {
    		try {
    			System.out.println("mapping verticle factory order : " + order() + ", class : " + getClass().getSimpleName());
    			String className = VerticleFactory.removePrefix(verticleName);
    			Class<? extends Verticle> clazz = (Class<? extends Verticle>) classLoader.loadClass(className);
    			Verticle v = clazz.newInstance();
    			promise.complete(() -> v);
    		} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
    			promise.fail(e);
    		}
    	}
    }
    
    public static class MyVerticleFactory2 extends MyVerticleFactory 
    {
    	@Override
    	public String prefix() { return "my"; }
       
    	@Override
    	public int order() { return 1; }
    }
    
    @Override
    public void start(Promise<Void> _prom) 
    {
    	vertx.registerVerticleFactory(new MyVerticleFactory());
    	vertx.registerVerticleFactory(new MyVerticleFactory2());
    	vertx.deployVerticle("my:vertx.MyVerticle");
    }

    정리 및 복습

    • 버티클이 배포될 때 버티클 팩토리에 의해 버티클 이름과 인스턴스를 매핑됩니다.
    • 새로운 매핑 규칙이 필요하면 버티클 팩토리를 직접 구현 할 수 있습니다.
    • 버티클 이름이 여러 개의 버티클 팩토리에 동시 매핑된다면, order() 메소드가 리턴하는 우선 순위값으로 선정됩니다.