한 프로젝트에 여러 클라우드 서비스를 사용해보자

1. 들어가며


  • 사내 제품 중에서는 하나의 코드베이스로 여러 사업을 진행하는 제품이 있습니다.
  • 아무래도 교육 플랫폼 서비스이기 때문에 활용도가 높은데요, 사기업의 임직원 강의를 위해서 사용될 수도 있으며 학교에서 초/중/고교생들의 학습을 위해서 사용되기도 합니다.
  • 아무래도 클라우드 시장 점유율의 32%라는 큰 파이를 차지하고 있는 AWS를 자사 서비스에서도 사용하고 있습니다.
    • 또한 각 클라우드 서비스마다의 장단점이 있어서, 이에 따라 상황에 맞게 AWS / GCP / NCP의 서비스를 사용하고 있습니다.
  • 다만, 사업에 따라서 특정 클라우드를 사용해야 하는 상황이 있는데요, 특히 공공기관/정부와 함께하는 사업이라면 국내 클라우드를 사용해야 합니다.
    • 국가에서 요구하는 클라우드 서비스 보안 인증(CSAP)에 대해 국내클라우드에서 기준을 충족할 수 있도록 구축(NCP)이 되어있기 때문입니다.

2. 상황


  • 프로젝트에서는 클라우드 서비스 기능 중 이미지 업로드 기능을 사용하고 있습니다.
    • 기존에는 aws용 이미지 업로드 모듈이 있었습니다.
    • 해당 모듈에서는 s3에 이미지 조회 / 업로드 / 삭제 / 리스트업 기능을 제공하고 있습니다.
  • 다만, 이제는 사업에 따라서 이미지 업로드 기능을 다르게 가져가야합니다.
    • NCP를 활용하는 사업일 경우 object storage를 사용해야합니다.
    • kakao cloud를 활용하는 사업일 경우 metakage를 사용해야합니다.
    • AWS를 활용하는 자사 사업일 경우 s3를 사용해야합니다.
  • 배포 인스턴스마다(환경변수) 각기 다른 클라우드 서비스의 이미지 업로드 기능을 사용해야합니다.

3. 문제점


  • 이제 각각 NCP / Kakao cloud / AWS용 버킷(object storage / metakage / s3) 모듈을 작성해야 합니다.
  • 이러한 모듈을 사업(상황)에 맞게 호출하기 위해 하나의 추상화된 모듈을 작성해야 합니다.
    • 추상화된 모듈을 호출하고, 사업에 맞게 알맞은 모듈을 호출하는 방식을 선택했습니다.

문제점


  • 각각의 서비스의 sdk를 활용해서 로직을 작성하는 부분은 어렵지 않은데요, 다만 NCP에서는 object storage를 사용하는데 있어 AWS S3 API를 호환하여 제공합니다.

  • 이로 인해서 기존의 AWS s3 모듈 코드를 똑같이 활용할 수 있는데요, 다만 생성해야하는 S3 객체가 다릅니다.

    • NCP의 경우 S3 객체에 gov-ncloudstorage endPoint를 명시해줘야 하는 등의 차이점이 있습니다.
  • 물론 아래와 같이 기존 AWS 코드에서 사업에 따라 분기처리하면 작동하기는 합니다.

    // aws.js
    
    // AWS를 사용한다면
    let s3 = null;
    
    if (global.STORAGE_TYPE === AWS) {
    	s3 = new AWS.S3({
    		apiVersion: '2006-03-01',
    		accessKeyId: global.ROOT_ACCESS_KEY,
    		secretAccessKey: global.ROOT_SECRET_ACCESS_KEY,
    	});
    } 
    
    // NCP를 사용한다면
    else if (global.STORAGE_TYPE === NCP) {
    	 s3 = new AWS.S3({
    		apiVersion: '2006-03-01',
    		accessKeyId: global.ROOT_ACCESS_KEY,
    		secretAccessKey: global.ROOT_SECRET_ACCESS_KEY,
    		signatureVersion: global.S3_SIGNATURE_VERSION,
    		endpoint: global.S3_ENDPOINT,
    	});
    }
    
    module.exports = {
    	s3,
    	async getObject() {
    	
    	},
      getObjectStream: () => {
        
      },
    	async copyObject() {
    		
    	},
    	async deleteObject() {
    	
    	},
    	async upload() {
    
    	},
    	async getObjects() {
    	
    	},
    	async deleteObjects() {
    	
    	}
    };
    
  • 하지만 관심 분리가 명확하게 되어있지 않아 모듈화의 이점을 살리기는 어렵다는 생각을 했습니다.

  • 그렇다고 해서, 중복되는 코드를 두개 작성하면(AWS용 / NCP 용) 관리 포인트가 두개가 늘어버리는 단점이 있었습니다.

    • AWS sdk에 변경점이 있다면 두개의 파일을 모두 수정해야 할 것입니다.

4. 해결방안


  • 해결방안을 고민하던 와중에 Proxy 객체를 떠올리게 되었습니다.
  • NCP용 모듈을 분리해서 ncp용 스토리지 객체를 만들되, 로직은 동일하기 때문에 기존 aws 모듈의 코드를 사용하는 방식을 택했습니다.
  • aws 코드를 사용하는 방식은 Proxy를 통해 기존 AWS 모듈의 메서드를 사용하는 방식입니다.
  • 해당 방식을 통해서 관심사가 분리된 모듈화의 이점도 살리면서, 불필요하게 관리포인트가 늘어나는 단점을 해결해나갈 수 있었습니다.

5. Proxy


  • 코드를 작성하기 이전에, Proxy 객체에 대해서 연구하고 작성하려고 합니다.

5.1 Proxy란


  • Proxy 객체를 사용하면 한 객체에 대한 기본 작업을 가로채고 재정의하는 프록시를 만들 수 있습니다.
  • 우선 아래 두가지 객체가 사용됩니다.
    • target: 프록시할 원본 객체
    • handler: 가로채는 작업과 가로채는 작업을 재정의하는 방법을 정의하는 객체입니다.

예시


const target = {
  message1: "hello",
  message2: "everyone",
};

const handler2 = {
  get(target, prop, receiver) {
    return "world";
  },
};

const proxy2 = new Proxy(target, handler2);

console.log(proxy2.message1); // world
console.log(proxy2.message2); // world
  • get() 처리기는 target 객체의 속성 엑세스를 가로챕니다.
    • proxy2.message1 / proxy2.message2 를 통해 속성 엑세스를 합니다.
    • 이러한 속성 엑세스가 있을때, get() 처리기가 가로채서 handler에 정의한 기능을 하도록 합니다.

5.2 Proxy 객체를 생성하는 법


ProxyConstructor 인터페이스


  • ProxyConstructor 인터페이스는 Proxy 객체를 생성하는 방법을 정의합니다.
  • 해당 인터페이스를 기준으로 살펴보겠습니다.
interface ProxyConstructor {
    /**
     * Creates a revocable Proxy object.
     * @param target A target object to wrap with Proxy.
     * @param handler An object whose properties define the behavior of Proxy when an operation is attempted on it.
     */
    revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };

    /**
     * Creates a Proxy object. The Proxy object allows you to create an object that can be used in place of the
     * original object, but which may redefine fundamental Object operations like getting, setting, and defining
     * properties. Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs.
     * @param target A target object to wrap with Proxy.
     * @param handler An object whose properties define the behavior of Proxy when an operation is attempted on it.
     */
    new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
declare var Proxy: ProxyConstructor;

5.2.1 revocable


  • 취소 가능한 Proxy 객체를 생성합니다.

  • 예시:

    const handler = {
      get(target, property) {
        return target[property];
      }
    };
    const target = { message: "Hello" };
    const { proxy, revoke } = Proxy.revocable(target, handler);
    console.log(proxy.message); // Hello
    revoke();
    // console.log(proxy.message); 
    // Error: revoke된 proxy에서는 'get'을 수행할 수 없다.
    

5.2.2 new


  • Proxy 객체를 생성합니다.

  • 예시:

    const handler = {
      get(target, property) {
        return target[property];
      }
    };
    const target = { message: "Hello" };
    const proxy = new Proxy(target, handler);
    console.log(proxy.message); // Hello
    

5.3 다양한 트랩 메서드, 사용법


  • proxyHandler 인터페이스를 기준으로 메서드들을 확인해보려고 합니다.
interface ProxyHandler<T extends object> {
    /**
     * A trap method for a function call.
     * @param target The original callable object which is being proxied.
     */
    apply?(target: T, thisArg: any, argArray: any[]): any;

    /**
     * A trap for the `new` operator.
     * @param target The original object which is being proxied.
     * @param newTarget The constructor that was originally called.
     */
    construct?(target: T, argArray: any[], newTarget: Function): object;

    /**
     * A trap for `Object.defineProperty()`.
     * @param target The original object which is being proxied.
     * @returns A `Boolean` indicating whether or not the property has been defined.
     */
    defineProperty?(target: T, property: string | symbol, attributes: PropertyDescriptor): boolean;

    /**
     * A trap for the `delete` operator.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to delete.
     * @returns A `Boolean` indicating whether or not the property was deleted.
     */
    deleteProperty?(target: T, p: string | symbol): boolean;

    /**
     * A trap for getting a property value.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to get.
     * @param receiver The proxy or an object that inherits from the proxy.
     */
    get?(target: T, p: string | symbol, receiver: any): any;

    /**
     * A trap for `Object.getOwnPropertyDescriptor()`.
     * @param target The original object which is being proxied.
     * @param p The name of the property whose description should be retrieved.
     */
    getOwnPropertyDescriptor?(target: T, p: string | symbol): PropertyDescriptor | undefined;

    /**
     * A trap for the `[[GetPrototypeOf]]` internal method.
     * @param target The original object which is being proxied.
     */
    getPrototypeOf?(target: T): object | null;

    /**
     * A trap for the `in` operator.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to check for existence.
     */
    has?(target: T, p: string | symbol): boolean;

    /**
     * A trap for `Object.isExtensible()`.
     * @param target The original object which is being proxied.
     */
    isExtensible?(target: T): boolean;

    /**
     * A trap for `Reflect.ownKeys()`.
     * @param target The original object which is being proxied.
     */
    ownKeys?(target: T): ArrayLike<string | symbol>;

    /**
     * A trap for `Object.preventExtensions()`.
     * @param target The original object which is being proxied.
     */
    preventExtensions?(target: T): boolean;

    /**
     * A trap for setting a property value.
     * @param target The original object which is being proxied.
     * @param p The name or `Symbol` of the property to set.
     * @param receiver The object to which the assignment was originally directed.
     * @returns A `Boolean` indicating whether or not the property was set.
     */
    set?(target: T, p: string | symbol, newValue: any, receiver: any): boolean;

    /**
     * A trap for `Object.setPrototypeOf()`.
     * @param target The original object which is being proxied.
     * @param newPrototype The object's new prototype or `null`.
     */
    setPrototypeOf?(target: T, v: object | null): boolean;
}

5.2.1 apply


  • 함수 호출을 가로챕니다.

  • 예시

    const handler = {
      apply(target, thisArg, argumentsList) {
        console.log(`Called with args: ${argumentsList}`);
        return target.apply(thisArg, argumentsList);
      }
    };
    const proxy = new Proxy(function() {}, handler);
    proxy(1, 2); // Called with args: 1,2
    

5.2.2 construct


  • new 연산자를 가로챕니다.

  • 예시

    const handler = {
      construct(target, args) {
        console.log(`Construct called with args: ${args}`);
        return new target(...args);
      }
    };
    const proxy = new Proxy(function() {}, handler);
    new proxy(1, 2); // Construct called with args: 1,2
    

5.2.3 defineProperty


  • Object.defineProperty()를 가로챕니다.

  • 예시

    const handler = {
      defineProperty(target, property, descriptor) {
        console.log(`Defining property ${property}`);
        return Reflect.defineProperty(target, property, descriptor);
      }
    };
    const proxy = new Proxy({}, handler);
    Object.defineProperty(proxy, 'prop', { value: 42 });
    

5.2.4 deleteProperty


  • delete 연산자를 가로챕니다.

  • 예시

    const handler = {
      deleteProperty(target, property) {
        console.log(`Deleting property ${property}`);
        return Reflect.deleteProperty(target, property);
      }
    };
    const proxy = new Proxy({ prop: 42 }, handler);
    delete proxy.prop; // Deleting property prop
    

5.2.5 get


  • 프로퍼티 접근을 가로챕니다.

  • 예시

    const handler = {
      get(target, property, receiver) {
        console.log(`Getting property ${property}`);
        return Reflect.get(target, property, receiver);
      }
    };
    const proxy = new Proxy({ prop: 42 }, handler);
    console.log(proxy.prop); // Getting property prop 42
    

5.2.6 getOwnPropertyDescriptor


  • Object.getOwnPropertyDescriptor()를 가로챕니다.

  • 예시

    const handler = {
      getOwnPropertyDescriptor(target, property) {
        console.log(`Getting descriptor for ${property}`);
        return Reflect.getOwnPropertyDescriptor(target, property);
      }
    };
    const proxy = new Proxy({ prop: 42 }, handler);
    Object.getOwnPropertyDescriptor(proxy, 'prop');
    

5.2.7 getPrototypeOf


  • Object.getPrototypeOf()를 가로챕니다.

  • 예시

    const handler = {
      getPrototypeOf(target) {
        console.log('Getting prototype');
        return Reflect.getPrototypeOf(target);
      }
    };
    const proxy = new Proxy({}, handler);
    Object.getPrototypeOf(proxy);
    

5.2.8 has


  • in 연산자를 가로챕니다.

  • 예시

    const handler = {
      has(target, property) {
        console.log(`Checking existence of ${property}`);
        return Reflect.has(target, property);
      }
    };
    const proxy = new Proxy({ prop: 42 }, handler);
    console.log('prop' in proxy); // Checking existence of prop
    

5.2.9 isExtensible


  • Object.isExtensible()를 가로챕니다.

  • 예시

    const handler = {
      isExtensible(target) {
        console.log('Checking if extensible');
        return Reflect.isExtensible(target);
      }
    };
    const proxy = new Proxy({}, handler);
    Object.isExtensible(proxy); // 'Checking if extensible'
    

5.2.10 ownKeys


  • Reflect.ownKeys()를 가로챕니다.

  • 예시

    const handler = {
      ownKeys(target) {
        console.log('Getting own keys');
        return Reflect.ownKeys(target);
      }
    };
    const proxy = new Proxy({ a: 1, b: 2 }, handler);
    console.log(Object.keys(proxy)); // Getting own keys
    

5.2.11 preventExtensions


  • Object.preventExtensions()를 가로챕니다.

  • 예시

    const handler = {
      preventExtensions(target) {
        console.log('Preventing extensions');
        return Reflect.preventExtensions(target);
      }
    };
    const proxy = new Proxy({}, handler);
    Object.preventExtensions(proxy);
    

5.2.12 set


  • 프로퍼티 설정을 가로챕니다.

  • 예시

    const handler = {
      set(target, property, value, receiver) {
        console.log(`Setting property ${property} to ${value}`);
        return Reflect.set(target, property, value, receiver);
      }
    };
    const proxy = new Proxy({}, handler);
    proxy.prop = 42; // Setting property prop to 42
    

5.2.13 setPrototypeOf


  • Object.setPrototypeOf()를 가로챕니다.

  • 예시

    const handler = {
      setPrototypeOf(target, prototype) {
        console.log('Setting prototype');
        return Reflect.setPrototypeOf(target, prototype);
      }
    };
    const proxy = new Proxy({}, handler);
    Object.setPrototypeOf(proxy, {});
    

6. Proxy로 모듈 작성하기


  • 위 Proxy 객체에 대해 알아본 바로, 현재 상황에서 사용할만한 트랩 메소드는 get()입니다.
    • 각 클라우드 서비스 객체 업로드 모듈을 추상화한 storage.js`모듈에서는, 각 메서드(e.g 업로드, 조회 등)가 프로퍼티로 존재합니다.
      • libStroage.upload()의 형식으로 호출됩니다.
    • 이러한 상황에서, get() 트랩 메소드를 통해 프로퍼티 접근을 가로채고, NCP에 대한 요청에 대해 코드 베이스가 같은 AWS 모듈을 호출해서 사용하려고 합니다.

6.1 구성 목표


flowchart TD C{storage.js} -->|AWS s3 객체| D[aws.js] C --> E[ncp.js] C --> F[metakage.js] E --> G{Proxy} G --> |NCP s3 객체| D
  • 사용단에서는 storage.js만을 호출합니다.
  • 환경변수에 맞게 storage.js에서는 metakage.js / aws.js / ncp.js 모듈을 호출합니다.
    • e.g)
      • STORAGE_TYPE === aws → aws.js
      • STORAGE_TYPE === ncp → ncp.js
      • STORAGE_TYPE === metakage → metakage.js
  • ncp.js의 경우 NCP를 바라보는 S3객체를 사용한다는 점 외에는 aws.js의 비즈니스로직이 동일하기 때문에, Proxy로 aws.js 모듈의 메서드를 S3객체를 매개변수로 넣어 호출합니다.

6.2 공용 모듈 작성


// 비즈니스에 맞게 필요한 클라우드 서비스의 모듈을 불러옴
const libStorages = {
	aws: require('../../libs/aws'),
	metakage: require('../../libs/metakage'),
	ncp: require('../../libs/ncp'),
};

const libStorage = libStorages[STORAGE_TYPE];

// 비즈니스에 맞게 옵션명을 변경해줌
const OPTION_NAME_MAP = {
	project: {
		aws: 'bucket',
		metakage: 'container',
		ncp: 'bucket',
	},
	...
};
const convertOption = (option) =>
	Object.keys(option || {}).reduce(
		(acc, optionName) => ({
			...acc,
			[OPTION_NAME_MAP[optionName]?.[STORAGE_TYPE] || optionName]:
				option[optionName],
		}),
		{}
	);

// 각 모듈의 실제 구현부를 호출
module.exports = {
	upload: async (option, callback) => {
		const uploadResult = await libStorage.upload(convertOption(option));
		callback?.(uploadResult?.data);
		return uploadResult;
	},
	getObjectStream: async (option) =>
		libStorage.getObjectStream(convertOption(option)),
	...
};
  • 다음과 같이 비즈니스에 따라 다른 모듈(AWS, NCP, metakage)이 호출되도록 공용 모듈을 하나 만들었습니다.
  • 해당 모듈로 인해서, 비즈니스 로직단에서는 어떠한 모듈을 사용할지에 대해 신경쓰지 않습니다.
    • e.g) storage.upload() 와 같이 호출하면, 이미 설정된 환경변수(STORAGE_TYPE)에 의해 의도된 모듈이 호출되고 사용됩니다.
  • 이러한 공용 모듈이 있는 상황에서, Proxy 객체를 활용해서 간단하게 AWS 로직을 사용하는 NCP 모듈을 만들어보겠습니다.

6.3 AWS, NCP 모듈


기존 AWS 모듈


// aws.js
const s3 = new AWS.S3({
		apiVersion: '2006-03-01',
		accessKeyId: global.ROOT_ACCESS_KEY,
		secretAccessKey: global.ROOT_SECRET_ACCESS_KEY,
	});

module.exports = {
	s3,
	async getObject(options, s3Instance = s3) {
		// code 
	},
  getObjectStream: (options, s3Instance = s3) => {
    // code 
  },
	async copyObject(options, s3Instance = s3) {
	  // code 
	},
	async deleteObject(options, s3Instance = s3) {
		// code 
	},
	async upload(options, s3Instance = s3) {
		// code 
	},
	async getObjects(options, s3Instance = s3) {
		// code 
	},
	async deleteObjects(options, s3Instance = s3) {
		// code 
	}
};
  • 기존에 사용하던 AWS s3용 메서드들이 모인 모듈입니다.
  • NCP에 대한 요청일 경우 전달받은 프로퍼티를 Proxy 객체로 해당 AWS 모듈의 프로퍼티를 호출하도록 할 것 입니다.
    • 호출할때, 옵션(key, bucket 등) 뿐만 아니라 NCP S3객체를 넘겨주도록 하였다.
  • 기존 AWS에 대한 요청일 경우 디폴트로 최상단에 선언한 AWS용 S3객체를 사용하도록 하였다.

NCP 모듈


const AWS = require('aws-sdk');

const aws = require('#core/libs/aws');

const ncpS3Instance = new AWS.S3({
	apiVersion: '2006-03-01',
	accessKeyId: global.ROOT_ACCESS_KEY,
	secretAccessKey: global.ROOT_SECRET_ACCESS_KEY,
	signatureVersion: global.S3_SIGNATURE_VERSION,
	endpoint: global.S3_ENDPOINT,
});

// ncp의 object storage는 aws-sdk를 같이 사용하지만 route53 기능을 지원하지 않음
const route53Methods = ['resizeCoverImage', 'getRecord', 'setRecord'];

const applyNcpS3Instance = (method) => (target, ...args) =>
	target[method](...args, ncpS3Instance);

// 메서드 맵핑 객체 생성
const methodMapping = {
	upload: applyNcpS3Instance('upload'), 
	getObjectStream: applyNcpS3Instance('getObjectStream'),
	getObjects: applyNcpS3Instance('getObjects'), 
	deleteObject: applyNcpS3Instance('deleteObject'),
	deleteObjects: applyNcpS3Instance('deleteObjects'),
	copyObject: applyNcpS3Instance('copyObject'), 
};

// 프록시를 사용하여 AWS SDK의 특정 메서드를 재정의
const awsProxy = new Proxy(aws, {
	get: (target, property, receiver) => {
		// Route53 메서드를 비활성화
		if (route53Methods.includes(property)) {
			return () => {};
		}

		// 메서드 맵핑을 사용하여 메서드를 재정의
		if (property in methodMapping) {
			return (...args) => methodMapping[property](target, ...args);
		}

		// 다른 모든 메서드는 원래의 메서드를 반환
		return Reflect.get(target, property, receiver);
	},
});

module.exports = awsProxy;
  • 우선 aws.js에 대한 프록시 객체를 만들었습니다.
  • get() 트랩 메서드를 통해 호출을 가로채고, 프록시로 aws.js 프로퍼티 메서드들을 사용하도록 하였습니다.

methodMapping, applyNcpS3Instance


  • upload / getObjectStream / getObjects / deleteObject / deleteObjects / copyObject 해당 메소드들에 대해서는 기존 옵션(bucket, key, 등) 과 NCP용 S3객체를 전달해서 실행해야 합니다.
const applyNcpS3Instance = (method) => (target, ...args) =>
	target[method](...args, ncpS3Instance);
  • target은 aws 모듈입니다.
  • …args로 전달받은 옵션을 모두 한번에 처리합니다.
  • 옵션들과 함께 ncp S3 객체를 넘겨줍니다.
await libStorage.upload(convertOption(option));
  • 위와 같이 사용하였을때, libStorage에서 NCP 모듈를 사용하게 되면 아래와 같이 해석됩니다.
await awsProxy.upload(options);
  • ncp모듈에서는 위와 같이 변경됩니다.
const awsProxy = new Proxy(aws, {
	get: (target, property, receiver) => {
		// Route53 메서드를 비활성화
		if (route53Methods.includes(property)) {
			return () => {};
		}

		// 메서드 맵핑을 사용하여 메서드를 재정의
		if (property in methodMapping) {
			return (...args) => methodMapping[property](target, ...args);
		}

		// 다른 모든 메서드는 원래의 메서드를 반환
		return Reflect.get(target, property, receiver);
	},
});
  • 이때 target은 aws 모듈, property는 upload가 됩니다.
  • property가 정의되어있는 methodMapping에 따라서 새롭게 반환하는데요,
// 메서드 맵핑 객체 생성
const methodMapping = {
	upload: applyNcpS3Instance('upload'), 
	... 
};

const applyNcpS3Instance = (method) => (target, ...args) =>
	target[method](...args, ncpS3Instance);
  • upload의 경우 applyNcpS3Instance('upload') 즉 target[method](...args, ncpS3Instance) = aws.upload(options, ncpS3Instance)로 변환됩니다
  • 이렇게해서 aws의 upload 메서드를 사용하면서 동적으로 매개변수로 ncpS3Instance를 전달하여 ncp에 업로드할 수 있게 만들었습니다.

최종 예시


  • 환경변수로 STORAGE_TYPE을 ncp로 설정해두고, upload 메소드를 사용하면 다음과 같이 작동합니다.
sequenceDiagram service.js->>+storage.js: libStorage.upload(options) storage.js-->>+ncp.js: target - aws.js / property - upload / ...args - option ncp.js-->>+aws.js: (proxy) upload(options, NCPS3Client) aws.js-->>- ncp.js: result ncp.js-->>- storage.js: result storage.js-->>- service.js: result
  1. service 단에서 storage.js 메소드 호출
  2. storage.js에서 환경변수에 맞게 모듈 호출 (ncp.js)
  3. ncp.js에서 proxy로 ncp s3객체와 함께 aws.js 호출
  4. 결과값을 모듈을 거쳐서 service로 전달

7. 마무리


  • 한 프로젝트에서 여러 클라우드 서비스를 사용하기 위해 모듈화를 진행해보았습니다.
  • 또한 중복된 코드로 인한 관리포인트 증가를 막기 위해 Proxy 객체를 활용해 보았습니다.
  • 모듈화를 통해 프로젝트 내에서 정적 스토리지 사용 방법이 통일되어 코드가 더 간단해지고 관리도 용이해졌습니다.
  • Proxy 객체의 내부 구현에 대해서도 더 알아보아야 하겠습니다.