■ MongoDB

[MongoDB] 몽고디비 인덱싱에 대하여..

인덱싱은 개념은 쉽지만 막상 대규모에 적용하려면 상당히 까다로운 문제에 직면하게 된다.

그중 몽고디비에서 아주 사소하지만 반드시 알아둘 것들을 정리해 보았다.

1. 몽고디비에서 부정(negation)에 대한 쿼리는 되도록 사용하지 말자.

부정에 대한 쿼리는 일반적으로 잘 작동하지 못할 수 있다. 작동을 잘 못한단 의미는 잘할수도 있고 아닐수도 있다는 의미다.

좀 더 깊이 가보자. 예를 들면

{
  nick: {
    $ne: /^hwa/
  }
}

다음과 같은 쿼리가 있다고 했을경우,  hwa로 시작되지 않는 문자열을 위해 모든 쿼리를 검색한다.

이는 hwa로 시작되는 문자열을 찾는 것과는 다르게 매우 비효율 적이다.

세세한거 따지기 싫다면 왠만하면 부정은 쿼리에 사용하지 말자. (부정의 반대는 긍정이므로 반드시 대칭되는 쿼리가 있을것이다.)

 

2. 위에서 /^hwa/ 라는 쿼리를 썼다. 정규식은 일반적으로 인덱싱이 되지 않는다. 하지만 접두어(prefix)의 경우 인덱싱이 가능하다.

 

3.  복합인덱스 순서에서 범위필드에 인덱싱은 항상 마지막으로 작성한다. 범위필드는 마지막에 해당하는 모든 쿼리를 찾고 최종적으로 제한하는 역할을 한다.

 

4. 몽고디비에서는 하나의 쿼리당 하나의 인덱스만을 사용할 수 있다. 하지만 OR쿼리는 $or: [] 안에 있는 배열의 개수만큼 인덱스를 이용하여 쿼리를 수행한다. 당연한 말이겠지만 여러개의 검색된 n개의 도큐먼트 집합이 있다는 의미이며, 몽고디비는 이 도큐먼트 집합들을 합치는데 또다른 비용을 소모한다. 가능하면 $in으로 가자.

 

5. 다음과 같은 내장된 도큐먼트에 대한 인덱스를 수행할 경우,

ensureIndex({'user.nick': 1});

반드시 쿼리문 작성 또한

find({'user.nick': 'hwarang'})

위 처럼 되야한다. 만약

find({user:{nick:'hwarang'}})

다음과 같다면 인덱스는 user에 대한 인덱스를 수행할 것이다.

 

6. 배열에 대한 인덱스는 다중키 인덱스라고 하는데, 이는 배열의 모든 요소에 인덱싱을 하는 것이다. 따라서 대체로 일반 인덱스보다 비용이 많이 든다. 또한 두개이상의 배열을 한번에 인덱싱 할수 없다. 예를들면

ensureIndex({friends: 1, family: 1});
find({friends:['1', '2'], family:['0', '1']};

다음과 같은 상황에서 friends와 family 두개의 키로 인덱스를 생성했는데 두개 모두 배열이다. 즉 두개이상의 배열을 한번에 인덱싱할 수없다는 원칙에 의거하여 에러가 날 것이다. 왜 안되는지에 대한 이유는, 다중키 인덱스는 각 요소마다 인덱싱을 한다고 했는데 2개의 배열은 n*m개 3개의 배열은 n*m*s개의 인덱스가 생성될 것이기 때문이다. (이는 심하게 비용낭비이다.)

 

7.  인덱싱할 필드는 카디널리티가 낮은것을 선택하자.  이유는 높은 카디널리티를 갖고 있는 필드는 인덱싱의 위력을 발휘하지 못한다. 낮은 카디널리티일수록 인덱싱의 진가가 발휘된다. (카디널리티는 한 필드의 중복성의 정도를 나타낸다. 만약 필드가 enum타입을 갖고 있다면 낮은 카디널리티가 된다. 가장 높은 카디널리티는 unique key (pk)가 된다.)

Standard
■ Javascript

[Angular.js] 앵귤러JS – ng-repeat에 있는 track by에 대한 설명

angular.js에서 ng-repeat를 사용할때track by란 것이 있습니다.

이것에 대해서 알아보도록하겠습니다.

ng-repeat는 directive입니다. 우리나라 말로 지시자라고 하는데, 앵귤러에서는 해당 지시자를 활용해서

여러가지 templete적인 부분과 plugin 형태의 개발을 함축해 놓았습니다.

 

ng-repeat를 이용해서 기존 템플릿의 for문을 구현할 수 있습니다.

<any ng-repeat="value in array"> </any>

를 사용하면 <any></any> 사이에 해당 scope가 자동으로 생기게 됩니다.

그러면서 $index, $first, $middle … 등과 같은 이미 정의된(pre-defined) 요소들의 값이 할당됩니다.

 

그런데 잘나가다가 하나 이상한 것이 들어오게 됩니다. 그것은 바로 track by란 놈인데,

표현식은 다음과 같습니다.

<any ng-repeat="value in array track by exp"> </any>

exp 부분에 해당하는 속성을 넣어주어야 합니다.

해당하는 속성이란 부분이 매우 애매한데 좀더 풀어서 설명하도록 하겠습니다.

자바스크립트에서 배열은 타 언어와 다르게 중복값을 허용합니다.

즉 array부분에 중복값이 있다면 반드시 distinct할 exp를 작성해 주어야 합니다.

쉽게 예제를 보면

 

<any ng-repeat="v in [0,0,1]"></any>

란 코드가 있을때 0,0 두개의 중복값이 있습니다. track by를 작성해 주지 않으면 각각은 모두 별개의 값으로서 인식되나 키값은 중복값을 허용하지 않게됩니다. ($$hashKey 이용)

여기선 $$hashKey에 키값으로 0이 2개가 들어가서 결국엔 [0, 1]과 같은 해쉬가 생성됩니다.  따라서 for문은 3바퀴를 도는데 값은 두개밖에 없습니다.

즉 에러가 나게 됩니다.

따라서

<any ng-repeat="v in [0,0,1] track by $index"></any>

라고 작성하면 $hashKey가 아닌 $index(위치)를 통해서 다른 3개의 DOM을 생성합니다.

 

그럼 간단하게 track by에 대해서 설명을 마치겠습니다. 안녕~

Standard
■ Android

안드로이드 앱내결제 (Android In App Billing)

오랜만에 안드로이드(Android) 포스팅입니다.

오늘은 안드로이드의 앱내결제(In App Billing, 이하 IAB)에 대해서 알아보도록 하겠습니다.

IAB는 어플리케이션 개발에서 매우 중요한 부분이기 때문에 결제플로우의 명확함, 보안성 등을 모두 고려해야합니다.

또한 현재 안드로이드에서는 draft상태인 앱에서는 결제테스트가 잘되지 않기 때문에 여러가지 고려해야할 상황에 놓이게 됩니다.

기본적인 SDK Manager나 기본상황은 모두 생략하겠습니다. 또한 개발환경은 Mac환경이고, 안드로이드 스튜디오(Android Studio)를 기준으로 설명합니다.

서버는 node.js 서버입니다.

 

1. 구현

1-1.  기본설정

먼저 aidl파일을 import해야합니다. 이파일은 SDK Manager로 다운받은 이후 설치된 sdk폴더를 기준으로

extras/google/play_billing 폴더 내에 있습니다. 또한 sample폴더내에서 MainActivity.java 파일을 찾으신후 그 옆에 있는 util 폴더를 복사해옵니다.

aidl은 (Android Interface description language) 인터페이스만 정의된 언어입니다.

간단하게 말해서 이기종 혹은 다른 방식의 안드로이드 앱들간, 프로세스간 등의 통신이 필요할때의 프로토콜이 되는 기준을 인터페이스만으로 정의한 것입니다.

여기선 IPC (Inter process communication) 를 위해서 사용됩니다.

스크린샷 2015-01-05 오후 3.14.36

폴더 구조는 다음처럼 됩니다. 이클립스와는 구조가 다르니 확인해보세요!

aidl안에 패키지가 정의되어있습니다.

이때 주의할 것은 aidl폴더내 패키지를 만들때 한번에 “com.android.vending.billing”으로 만들면 안되고,

“com” 을 먼저 만들고 그 내부에 “android” 그내부에 “vending”, “billing”이런식으로 만들어야 합니다.

즉 패키지가 실질적으로 “com/android/vending/billing”의 형태가 될 것입니다.

다음으로는 안드로이드 기본

2-2. 랩퍼 구현.

이제 구현을 쉽게 하기위해 추상화된 형태의 헬퍼클래스를 구현해보도록 하겠습니다.

코드설명은 주석으로 읽어보시면 되겠습니다.

public class InAppBillingHelper {

    public static final String TAG = "InAppBillingHelper";
    
    // onActivityResult에서 받을 requestCode.
    public static final int REQUEST_CODE = 1001;

    // publicKey 개발자콘솔에서 앱을 생성후 얻을 수 있다.
    private String mPublicKey;
    
    // 테스트를 하기 위한 테스트용 productId(SKU)
    private static final String TEST_SKU = "android.test.purchased";

    // 구매되고 소진되지 않은 아이템을 캐시해놓을 변수.
    private ArrayList<String> mOwnedItems = new ArrayList<>();
    
    // 가져온 util클래스에서 제공하는 클래스. 아이템리스트를 갖고 있다.
    private Inventory mInventory;
    
    // 가져온 util클래스의 실제 헬퍼클래스
    private IabHelper mHelper;
    
    // 자체적으로 서버와 통신할 서비스 클래스. (여기선 구현은 생략)
    private ItemService mItemService;
    
    private Activity mActivity;
    
    // 테스트 여부인지.
    private boolean mIsTest;

    // 실제 우리가 알고있는 아이템목록. (어플리케이션 서버로부터 받아오면됨.)
    public List<String> mItems = new ArrayList<>();

    // 로드 이후 호출될 리스너.
    public interface InventoryLoadListener {
        public void onBefore();
        public void onSuccess(Inventory inventory);
        public void onFail();
    }

    public void init(Activity activity) {
        mActivity = activity;
        mItemService = new ItemService(activity);
        mIsTest = false;
    }

    // 공개키가 없는 생성자.
    public InAppBillingHelper(Activity activity) {
        init(activity);
    }

    // 공개키가 있는 생성자.
    public InAppBillingHelper(Activity activity, String publicKey) {
        mPublicKey = publicKey;
        init(activity);
    }

    // 테스트 여부의 세터.
    public void setTest(boolean isTest) {
        mIsTest = isTest;
    }
}

 

아주 기본적인 클래스입니다.

우리는 IabHelper를 통해서 실질적으로 통신할 것입니다. 이 헬퍼클래스는 내부적으로 구글 서버와 통신하여 우리에게 응답을 주는 코드들을 랩핑해 놓은 것입니다.

또한 ItemService는 신경쓰지 않아도 됩니다. 자체적으로 어플리케이션 서버와 통신하기 위한 Rest클래스 입니다.

이어서 계속 코딩해 보겠습니다.

public class InAppBillingHelper {

    ....

    public void startSetup(ArrayList<String> items, final InventoryLoadListener listener) {
        // before() 를 호출함 으로서 로딩바를 보여주는 등의 액션을 취할 수 있다.
        listener.onBefore();

        // 실제 구글 서버에 요청할 sku리스트.
        mItems = items;

        // 서버에서 테스트 sku목록까지 넣어놓았었다면 제외.
        for (int i = 0; i < mItems.size(); ++i) {
            if (mItems.get(i).equals(TEST_SKU)) {
                mItems.remove(mItems.get(i));
                break;
            }
        }

        // 실제 util에서 가져온 헬퍼생성.
        mHelper = new IabHelper(mActivity, mPublicKey);

        // startSetup함수를 호출함으로서 현재 통신가능여부를 확인하고, 정상적으로 커넥션이 이루어졌는지 확인할 수 있다.
        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {

            @Override
            public void onIabSetupFinished(IabResult result) {
                if (!result.isSuccess()) {
                    listener.onFail();
                } else {
                    // 성공적으로 연결이 되었다면 이제 실질적인 아이템을 로드해야한다.
                    loadItemInventory(listener);
                }
            }
        });
    }
}

이제 초기화를 위해 실질적으로 startSetup메소드를 호출하였습니다. 최종적으로는 loadItemInventory를 구현함으로서 마무리될 것이다.

해당 메소드는 아래에서 구현하도록 하겠습니다.

public class InAppBillingHelper {

    ....

    // 아이템을 로드하는 메소드
    private void loadItemInventory(final InventoryLoadListener listener) {
        // 실제 아이템목록을 받아올 수 있음.
        // mItems에 올바른 값이 할당되어야 함.
        // 만약 mItems가 null값이라면 queryInventoryAsync메소드는 콜백이 호출될때 성공했다고 나오지만
        // 어떠한 아이템 리스트 값도 받아올 수 없음.
        mHelper.queryInventoryAsync(true, mItems, new IabHelper.QueryInventoryFinishedListener() {

            @Override
            public void onQueryInventoryFinished(IabResult result, Inventory inv) {

                // IabResult 값에는 다양한 에러코드및 성공여부를 담고 있음.
                if (result.isSuccess()) {
                    // 멤버변수로 받아온 아이템리스트를 갖고 있는 inventory를 담음.
                    mInventory = inv;

                    // mInventory에는 구매는 했지만 소진되지 않은 값을 얻어올 수 있음.
                    // 따라서 새롭게 받아온 인벤토리를 통해 갱신하기 위해 클리어.
                    mOwnedItems.clear();

                    // 이제 mItems의 sku값을을 갖고 inv에 조회하여 소진되지 않은 아이템 목록을 담음.
                    for (String sku : mItems) {
                        if (inv.hasPurchase(sku)) {
                            mOwnedItems.add(sku);
                        }
                    }

                    // 테스트sku는 가끔 소진되지 않을 때가 있음. 이럴땐 무조건 헬퍼가 수행될 때 소진시킴.
                    if (mInventory.hasPurchase(TEST_SKU)) {
                        // consumeItem에 첫번째 인자는 해당 sku이며, 두번째 인자는 Purchase 인스턴스가 됨. 이후에 다시 설명.
                        consumeItem(TEST_SKU, null);
                    }

                    listener.onSuccess(inv);
                }
                else {
                    listener.onFail();
                }
            }
        });
    }
}

 

여기서 아이템 소진, 구매가 별도로 있다는 것을 알 수 있습니다. 보통 플로우로, 먼저 아이템을 구매하고, 구매가 성공하면 어플리케이션 서버에 아이템구매요청을 해서 실제 어플리케이션 내부에서 쓰일 캐시나 아이템을 지급하고, 요청이 성공되면 comsume 즉 소진을 하여 다시 재구매가 가능하게 합니다.

여기서는 즉 unmanaged item에 대해서 설명한 것인데, unmanaged는 소진가능한 아이템이라고 보시면 됩니다. 그밖에도 managed 아이템 및 subscription 아이템이 있습니다. managed 아이템은 한번 구매하면 다시 구매할 수 없는 아이템이며,subscription 같은 경우는 말그대로 구독아이템입니다. (기간을 정해놓고 일정 기간간격으로 지속적으로 구매되어지는 아이템)

여기서는 아이템을 구매하고 해당 앱에서 처리를 한 후 재구매가 된다고 가정하였습니다.

    

public class InAppBillingHelper {

    ....

    // 실제 구매 (인앱결제)
    public void purchaseItem(final String sku) {

        // 만약 test가 true라면 테스트SKU를 갖고 구매요청을 한다.
        final String refinedSKU = mIsTest ? TEST_SKU : sku;

        // 구매후 소진되지 않은 아이템이라면 다시 구매할 수 없다.
        if (!mOwnedItems.contains(refinedSKU)) {

            // 실제 구매 & 결제요청
            // 해당 메소드를 호출하면 내부적으로 팝업창형태의 결제창이 나온다.
            mHelper.launchPurchaseFlow(mActivity, refinedSKU, REQUEST_CODE, new IabHelper.OnIabPurchaseFinishedListener() {

                @Override
                public void onIabPurchaseFinished(IabResult result, final Purchase info) {
                    if (result.isSuccess()) {

                        // 구매되고 소진되지 않은 목록에 캐시.
                        mOwnedItems.add(info.getSku());
                        
                        // 실제 어플리케이션 서버에서 소진되도록 호출.
                        consumeItemForServer(info);
                    } else {
                        if (result.getResponse() == 0) {
                            // 구매가 실패하였고 이유가 아직 소진되지 않은 것이라면
                            // 소진을 위해 서버에 요청.
                            consumeAllItemsForServer();
                        }
                        CommonHelper.showMessage(mActivity, result.getResponse() + "");
                        ErrorHandler.handleLocalError(mActivity, LocalErrorCode.ITEM_PURCHASE_ERROR);
                    }
                }
            });
        } else {
            // 이미 구매한 아이템이라면 모두 소진시킨다.
            consumeAllItemsForServer();
        }
    }
}

 

이제 구매요청을 합니다. 여기서 REQUEST_CODE가 있는데, 바로 이 요청을 하는 순간 해당 mActivity의 onActivityResult를 통해서 응답을 준다는 의미입니다.

만약 구매가 성공하면 소진을 위해 어플리케이션 서버에 요청을 하게 됩니다.

public class InAppBillingHelper {

    ....

    // 외부 액티비티나 프래그먼트의 onActivityResult함수에서 호출해야 한다.
    // 해당 메소드가 호출되지 않으면 정상적으로 프로세스가 끝나지 않는다.
    // mHelper.handleActivityResult 내부에서 onIabPurchaseFinished메소드가 호출된다.
    public void onActivityResult(int requestCode, int resultCode, Intent data){
        if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
            onActivityResultError(requestCode, resultCode, data);
        }
    }

    // 해당 메소드를 상속받아 오버라이딩하여 에러를 처리할 수 있다.
    public void onActivityResultError(int requestCode, int resultCode, Intent data) {

    }
}

이제 onActivityResult에서호출할 헬퍼 메소드를 작성합니다.

에러처리는 콜백이 아닌 상속을 통해서 해결하였습니다.

 

public class InAppBillingHelper {

    ....

    // 실제 어플리케이션 서버에 아이템 구매요청을 한다.
    private void consumeItemForServer(final Purchase purchase) {

        final String refinedSKU = mIsTest ? TEST_SKU : purchase.getSku();

        // mItemService는 내부적으로 rest방식의 통신을 어플리케이션 서버와 해주는 통신인스턴스이다.
        // 각자 서버와 통신에 맞게 구현.
        mItemService.purchaseItem(refinedSKU, purchase.getToken(), new ItemPurchaseListener() {
            @Override
            public void onBefore() {
                FullScreenProgressBar.show(mActivity);
            }

            @Override
            public void onSuccess() {
                if (mActivity != null) {
                    FullScreenProgressBar.hide();
                }
                CommonHelper.showMessage(mActivity, "Success!~");
                // 통신이 성공하면 실제 소진을 시킨다.
                // 여기서 두번째 인자로 purchase 인스턴스를 준다.
                // 이때에는 mInventory가 갱신이 되어 있지 않는 경우가 종종있다.
                // 따라서 mInventory에서 purchase를 가져오는 것이다니고
                // 해당 구매 콜백으로 부터 가져온다.
                consumeItem(refinedSKU, purchase);
            }

            @Override
            public void onFail(com.slogup.frameworks.models.Error error) {
                if (mActivity != null) {
                    if (error != null) {
                        ErrorHandler.handleNetworkError(mActivity, error);
                    }
                }
            }
        });
    }
}

이제 어플리케이션 서버에 구매요청을 하고 성공하면 소진시키기 위해 consumeItem을 호출합니다.

여기서 mInventory갱신 문제가 있는데, 실제 구매를 하면 mInventory의 값이 갱신된다. 하지만 갱신이 잘되지 않는 경우가 종종있어서

mInventory.getPurchase(sku)를 호출하기 보단 purchase값을 그대로 가져다 쓰는게 좋습니다.

만약 여기서 null을 입력하면 mInventory.getPurchase(sku)를 통해서 가져옵니다.

 

// 실제 소진 처리.
    private void consumeItem(String sku, Purchase purchase) {

        final String refinedSKU = mIsTest ? TEST_SKU : sku;

        if (purchase != null || mInventory.hasPurchase(refinedSKU)) {

            // purchase가 있다면 해당 구매 인스턴스를 통해 처리하고, 그게 아니라면 mInventory를 통해처리
            // 보통 구매성공, 소진실패일 경우일때 mInventory.hasPurchase(refinedSKU)를 통해서 가져오면 된다.
            Purchase refiendPurchase = (purchase == null) ? mInventory.getPurchase(refinedSKU) : purchase;
            
            // 소진처리.
            mHelper.consumeAsync(refiendPurchase, new IabHelper.OnConsumeFinishedListener() {
                @Override
                public void onConsumeFinished(Purchase purchase, IabResult result) {
                    if (result.isSuccess()) {
                        // 소진에 성공하면 캐시된 값을 지운다.
                        mOwnedItems.remove(purchase.getSku());
                    } else {
                        ErrorHandler.handleLocalError(mActivity, LocalErrorCode.ITEM_FATAL_ERROR);
                    }
                }
            });
        }
        else {
            ErrorHandler.handleLocalError(mActivity, LocalErrorCode.ITEM_FATAL_ERROR);
        }
    }

이제 소진처리를 해봅니다. 특별한 것은 없고 헬퍼메소드를 호출하고 캐시된 값만 지우면 됩니다.

 

public class InAppBillingHelper {

    ....

    private static int sPurchaaseCount = 0;
    public void consumeAllItemsForServer() {

        if (mOwnedItems.size() > sPurchaaseCount && mOwnedItems.get(sPurchaaseCount) != null) {

            final String sku = mOwnedItems.get(sPurchaaseCount);
            Purchase purchase = mInventory.getPurchase(sku);

            if (purchase != null) {

                String token = purchase.getToken();
                mItemService.purchaseItem(sku, token, new ItemPurchaseListener() {

                    @Override
                    public void onBefore() {
                        FullScreenProgressBar.show(mActivity);
                    }

                    @Override
                    public void onSuccess() {
                        sPurchaaseCount++;
                        consumeItem(sku, null);
                        consumeAllItemsForServer();
                    }

                    @Override
                    public void onFail(com.slogup.frameworks.models.Error error) {
                        if (mActivity != null) {
                            if (error != null) {
                                ErrorHandler.handleNetworkError(mActivity, error);
                            }
                        }
                    }
                });
            }
            else {
                handleError();
            }
        }
        else {
            handleError();
        }
    }

    private void handleError() {
        sPurchaaseCount = 0;
        mOwnedItems.clear();
        if (mActivity != null) {
            FullScreenProgressBar.hide();
        }
    }
}

마지막코딩으로 재귀적으로 소진요청에 실패했던 값들을 모두 소진시키는 함수를 만듭니다.

여기서 주의할 점은 이미 서버상에서 소진된아이템은 어플리케이션 서버에서 저장하고 값을 비교해서 적절하게 처리해줘야 한다는 점입니다.

 

이제 한가지만 더 세팅해주면 됩니다. 그것은 바로 퍼미션!

<uses-permission android:name="com.android.vending.BILLING" />

이렇게 간단하게 헬퍼메소드를 만들었습니다. 이제 개발자콘솔을 설정해 보도록 하겠습니다.

 

2. 개발자콘솔 설정.

2-1. 앱생성

먼저 개발콘솔에 접속합니다. https://play.google.com/apps/publish/

다음으로 Add new application을 눌러 새로운 어플리케이션을 만듭니다.

스크린샷 2015-01-05 오후 3.05.11

 

이어서 Title을 적고 Prepare Store Listing버튼을 누릅니다.

여기서 title은 현재 자신이 갖고 있는 앱리스트에서 key값이 아닙니다. 즉 중복 타이틀이 가능하다는 이야기입니다. (키가 되는것은 패키지명입니다.)

 

2-2.  앱 업로드

앱은 테스트를 위해 Application Id값을 변경해서 올려주시기 바랍니다. 왜냐하면 publish가 되어야 테스트가 가능한데, 그렇게 되면 해당 패키지명을 계속 써야하기 때문입니다. 위에 퍼미션 설정이 되어있지 않은 앱은 인앱 아이템리스트를 넣어줄 수 없습니다.

스크린샷 2015-01-05 오후 4.23.48

 

앱이 올라가고, published가 된상태가 되면 다음과 같은 화면을 볼수 있습니다.

스크린샷 2015-01-05 오후 4.26.37

 

위에 보는 바와 같이 unmanaged product로 샘플 아이템이 등록되어있습니다.

또한 status가 active상태로 해놔야 실제로 응답을 받을 수 있습니다.

추가적으로 우리는 공개키가 필요합니다. 왼쪽 메뉴중 젤아래 Services & APIs에 가면 공개키를 쉽게 얻을 수 있습니다.

 

2.3. 테스터 등록.

다음으로 테스터를 등록하고, 어플리케이션서버에서 적절하게 벨리데이션할수 있도록 준비해 보도록 하겠습니다.

스크린샷 2015-01-05 오후 4.30.30

해당 설정에서 API access탭으로 가면 제일 아래 Service accounts라는것이 보입니다. create service account버튼을 눌러 안내에 따라 계정을 만듭니다.

해당 계정은 어플리케이션 서버와 구글 인증서버간 OAuth요청을 위해 필요합니다.

여기서 왜 OAuth인증이 필요한지는 바로 해당 구매된 아이템을 verify하기위한 uri 리소스를 구글에서 제공해주는데 이때 로그인이 필요합니다. 하지만 우리는 verify를 어플리케이션 서버내에서 호출해야하고 따라서 두 서버간 (어플리케이션 서버가 클라이언트역할) 인증을 위해 OAuth를 이용하는 것입니다.

이제 아래 그림과 같이 acocunt details탭으로 이동합니다.

스크린샷 2015-01-05 오후 4.30.06

 

제일 아래 Licensse testing이란 제목이 보이는데 여기서 방금 얻은 계정을 등록합니다. 또한 실제 디바이스에서 테스트할때 로그인되어있을 구글계정(이메일)을 등록합니다. (컴마로 구분)

스크린샷 2015-01-05 오후 4.36.26

 

다음으로 user accounts & rights 탭으로 이동합니다. 그리고 invite new user버튼을 눌러 oauth를 위해 생성한 계정을 초대하고 권한을 View financial reports 가 체크되도록 줍니다. 보통 관리자로 주면 됩니다.

다음으로 API access탭의 Service accounts에서 create service account버튼을 누르면 나타나는 개발자콘솔 페이지링크로 이동하여 API 및 인증 탭에 사용자 인증정보로 이동합니다.

스크린샷 2015-01-05 오후 4.41.48

여기서 새클라이언트 ID만들기를 하고 얻은 값들은 무시하고 새 JSON키 생성버튼을 눌러 얻은 값을 저장해 놓습니다.

그럼 이제 테스터 등록 및 서버에서 유효성검사를 하기 위한 준비를 맞췄습니다.

 

서버에서 유효성 검사하는 부분은 다음 포스팅에서 해보도록 하겠습니다.

그럼 안녕~~

 

 

 

Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (7)

이번에는 자바스크립트의 함수 메모리 구조에 대해서 살펴보도록 하겠다.
먼저 메소드 오버로딩에 대해서 살펴보겠다.
객체지향언어에서 메소드오버로딩은 같은 메소드명을 가졌지만 시그니쳐중에서, 인자값의 개수나 형이
다른것을 의미한다.
하지만 자바스크립트에서는 메소드오버로딩이 동작하지 않는다.
오히려 그보다 훨씬더 유연하게 개발이 가능하다.
예를 하나 보자.

function add(a, b) {
    var total = 0;
    for (var i = 0; i < arguments.length; ++i) {
        total += arguments[i];
    }
}

먼저 add(10); 이렇게 호출하면 a = 10이 처음 할당되고, b에는 undefined가 할당된다.
즉 파라미터 처음부터 값이 할당되고 호출부분에서 인자수가 부족하다면 나머지 값은 모두 undefined로 채워진다.

다음으로는 선언한적도 없는 arguments라는 배열이 보인다. 하지만 이것은 배열이 아니다. 자바스크립트에서 배열처럼 보이게
만들어준 것일 뿐이다. 따라서 Array객체의 함수들은 사용할수 없고 다면 length나 []연산자를 제공한다.

다시 함수 메모리 구조로 돌아와 보겠다.
먼저 함수의 구조를 살펴보겠다.

함수는 위에서 오버로딩을 설명하면서 이야기했던 arguments와 a,b같은 인자값을 하나의 객체로 들고 있는 호출객체가 있다.
위 코드를 살짝 바꿔보겠다.
add(10)을 호출하는 순간 내부에서는

add({a:10, b:undefined, arguments:[10]});

이렇게 바뀐다. 즉 10만 호출했을 뿐인데 저렇게 하나의 호출객체가 들어간다는 의미이다. 그리고 저 객체는 이름이 없다.
즉 add함수 내부에서는 저 객체가 전역객체 처럼 작용한다.
전역객체란 말이 나왔는데 바꿔말하면 우리가 브라우져스크립팅을 할때 내부에 코드를 작성하면
우린 window란 전역객체가 생긴다. 또한 window는 생략이 가능하다.
console.log(); 이부분도 window.console.log()처럼 될수 있다는 의미이다.

var console = {
    log:function(…){}
};

(function(??){
    console.log(1);
})({console:console});

이런식으로 사용하는것이 생략되었다고도 가정할 수 있다.

현재 메모리구조를 설명하면서 함수의 호출객체에 대해서 설명했다. 즉 함수가 만들어지면 저러한 호출객체가 자동으로 만들어 진다는 의미이다.
다음 구조를 보도록하겠다.
다음으로는 prototype 객체가 하나 만들어진다. 하지만 이것은 일반적인 우리가 알고있는 객체와는 조금 다른 성질을 지녔다.
이부분은 나중에 다시 설명하도록하겠다.

다음구조는 실제 코드가 올라가는 영역이 필요하다.
이부분에서 선언되어진 변수들은 객체지향에서 private메소드, 속성과 대응된다.

마지막 구조는 함수의 속성값을 달수 있다는 부분이다. 함수는 앞에서 말한 일반 객체가 아니다. 그럼에도 불구하고 저렇게 사용할 수 있으며, public 메소드와 대응될수도 있으며, 만약 add를 Addition라고 대문자 명사로 바꾸면서 생성자로서 사용한다고 하면
static 메소드, 속성이 될수도 있다는 의미이다.

add는 함수이다. 하지만 자바스크립트에서 함수는 first class function 즉 일반 객체처럼도 사용이 가능하다는 의미이다.
따라서 add.a = 10; 이렇게 사용이 가능하며 마지막 구조인 “함수의 속성값을 달수있다”라는 부분이 나타난다.

정리하면 함수의 구조는
호출객체, prototype객체, 코드영역, 공개영역 이렇게 나누어진다.

살짝더 정확히 이야기하면 코드영역 즉 private영역에 예외적으로 (자바스크립트엔진에 의해서) prototype을 가르키는 객체가 하나더 있으며 이는 앞서 말한 prototype객체를 참조한다.

다음시간에는 객체 참조 체인에 대해서 알아보도록하겠다.

Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (6)

이제 자바스크립트의 핵심인 함수에 들어가보도록 하겠다.

 

1.함수

자바스크립트에서 함수는 First Class 함수라고 한다.
이말은 함수가 다음의 3가지 유형으로 사용이 가능하다는 의미이다.

1. 변수에 담을 값
2. 함수의 인자 값
3. 함수의 리턴 값

또한 다음의 3가지 말고도 다양한 기능이 있다.
함수는 생성자가 될수도 있다. 즉 new 키워드와 함수를 사용하면
자바스크립트에서 특별한 프로세스로 해당 생성자로 만들어진 인스턴스를 만들어 준다.
마지막으로 함수의 일반적인 기능인 (즉 타 언어에서의 진짜 함수의 목적) 호출가능한
루틴으로서의 역할이 있다.

정리를 하자면 크게 3가지의 역할

1. 일급함수의 역할
– 변수에 담을 값
– 함수의 인자 값
– 함수의 리턴 값
2. 생성자의 역할
3. 일반 루틴의로서의 역할

하나씩 짚고 넘어가 보자,

먼저 일급함수로서의 역할을 보자.

함수는 다음과 같이 두가지형태로 선언이 가능하다.

1. function FunctionName() {} // (파싱단계)

2. var FunctionName = function() {} // (실행단계)

둘다 함수를 만드는 방법이며 차이점은 수행단계에 대한 차이점 뿐이다.
그렇다면 일급함수의 3가지 역할을 이제 대입해보자.
먼저 일급함수의 1번의 역할은 이미 2번째 형태로 표현했다.
즉 var 키워드로 만든 변수에 함수를 대입했다.

다음은 함수의 인자값으로 설명해보겠다.

function OuterFunction(callbackFunction) {
    callbackFunction();
}

OuterFunction(FunctionName);

다음처럼 호출이 가능하다. 즉 파라미터로 바로 함수를 넘길수 있다.
위의 예문은 간단한 callback 함수의 설명이다.

마지막으로 함수의 리턴값을 보도록 하겠다.

function OuterFunction2() {
var InnerFunction() { return 10; };
    return InnerFunction();
}
console.log(OuterFunction2()());

뒤에 ()가 2번있는 것이 이상하게 보일수도 있지만, 정상적인 문법이다. 즉 리턴된 함수를 이어서 바로 호출 했기 때문에
()가 2번 호출된 것이다. 콘솔에는 10이 찍힐 것이다.

다음으로 함수를 생성자의 역할로서 살펴보도록 하겠다.
new키워드와 함께 사용되면 함수를 생성자의 역할로 활용한다고 하였다. 그렇다면 어떻게 생성자가 된다는 것일까?
이러한 물음에 가장 가까운 키워는 바로 this이다. 일반 객체지향언어에서 this는 해당 인스턴스 자신을 가르킨다.
즉 this는 class를 가르키는 것이 아니고 instance를 가르킨다는 점이다.

그렇다면 자바스크립트는 어떨까? 자바스크립트에서는 클래스가 없다. 이말이 이상하게 들릴수도 있다. 하지만 함수의 생성자로서의 역할을
생각해보면 납득이 갈 것이다. 즉 함수가 클래스와 비슷한 역할을 수행한다고도 볼수있다.(엄연히 말하면 다르지만 일단은 그냥 넘어가자.)

function Constactor() {
    this.name = “name”;
    this.lv = 10;
    function getLevel() {
        return this.lv;
    }
    this.getLevel = getLevel;
}

new 키워드 뒤에 함수가 호출이 되면, 먼저 함수 내부에서 this란 가상변수를 만들고, (여기서 가상변수란 우리가 명시적으로 선언하지 않은 변수를 말한다.)
해당 this에 새로운 오브젝트를 넣는다.

var this = new Object();
이런식으로 V8과 같은 엔진이 처리를 해주는 것이다.
다음으로 함수 내부에 this.name, this.lv등처럼 오브젝트에 프로퍼티를 할당해준다. (이것이 가능한 이유는 자바스크립트에서는 동적으로 바로 프로퍼티를 추가할 수 있기 때문이다.)
마지막 getLevel을 보자. 파싱단계에서 function키워드를 통해 getLevel을 해당 스택에 정의할 것이다.
다음으로 this.getLevel 프로퍼티(여기서는 메소드)에 해당 getLevel포인터를 넣는다. getLevel과 this.getLevel은 다른 영역에 있기 때문에 중복되지 않는다.
마지막으로 자동으로 return this;를 수행하면서 new키워드의 역할을 수행하는 것이다.
최종 코드를 보자. 해당 코드는 우리가 작성하지 않은 가상 변수를 합친 것이다.

function Constactor() {
    var this = new Object();
    this.name = “name”;
    this.lv = 10;
    function getLevel() {
        return this.lv;
    }
    this.getLevel = getLevel;
    return this;
}

마지막으로 일반함수 루틴으로서의 역할은 생략한다.
다음에는 해당 함수의 메모리 구조에 대해서 살펴보도록 하겠다.

Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (5)

1. 논리연산자
자바스크립트에서는 논리연산자가 특별하게 사용된다.
다음 코드를 보자.

var a = true;
var b = “hello”;
var c = 0;
var d = null;
var e = “”;

var result = a || b;
var result2 = b && c;
var result3 = c && d;
var result4 = d || a && b;
var result5 = a && b || c;
var result6 = e || a;

각각의 result값은 어떻게 될까?

논리연산자를 일반 변수에 사용한 것이 이상하게 보일 수 있다.
하지만 자바스크립트에서는 이것이 허용되며 다른 언어와는 다른 특별한 의미도 갖고 있다.

각 결과는 다음과 같다.

result = true;
result2 = 0;
result3 = 0;
result4 = “hello”;
reulst5 = “hello”
result6 = true;

이것을 보고 유추를 할수 있는 것은 다음과 같다.

X || Y 에서 X가 거짓이면 Y를 반환한다. 그리고 X가 참이면 X를 그대로 반환한다.
X && Y 에서 X가 거짓이면 X를 반환한다. 그리고 X가 참이면 Y를 반환한다.

간단한 실제 예를 들어보겠다.

if (param && param.user && param.user.lv >= 100) {
//blabla
}

위 코드는 param이 존재하면 param.user를 반환하고, param.user가 존재하면 param.user.lv을 반환하고 100과 비교하여
참 혹은 거짓을 판별한다.
이때 param이 거짓이면 바로 거짓으로 판단하고 해당 if문을 수행하지 않는다.

다음으로 유추할 수 있는것은
거짓으로 구분하는 형태들이다.

자바스크립트에서는

null, undefined, 0, “”, false, NaN 은 모두 거짓으로 판단한다.

2. null 과 undefined
null은 명시적으로 데이터가 없다는 것을 의미한다.
반면 undefined는 데이터의 초기값이 정해지지 않았을때 할당된다.

두 값 모두 거짓을 의미한다.

var a = null;
var b;

if (a == b) {
// blabla
}

위와 같은 코드가 있을때 if문 로직을 수행할까?
정답은 수행한다 이다.

그렇다면

if (a === b) {
// blabla
}

위와같은 코드는 어떨까? if문 로직을 수행할까?
해당 로직은 수행하지 않는다.

== 비교와 === 비교가 있다.
== 비교는 비교하기전에 두 피연산자를 같은 타입으로 변환시킨 후 비교한다.
하지만 ===비교는 해당 타입 그대로 비교하기 때문에 타입과 값이 모두 같아야 한다.

3. for문

자바스크립트에서는 for in 문이 존재한다.

var persons = {a:1,b:2,age:24};
for (var key in persons) {
    console.log(key, persons[key]);
}

처럼 객체또한 for문을 돌릴 수 있다.

하지만 어쩔수 없는 상황이 아니라면 for in문은 자재하도록 하자.
그 이유는 뒤에서 다시 알아보도록하자 일단
성능이 급격히 저하된다는 사실을 인지하면된다.

또한 자바스크립트에서는 최대한 a.b.c.d.e 와같은 접근을
var f = a.b.c.d.e;
로 해놓고 f로 접근 하는 것이 좋다.
이또한 비슷한 이유이다.

Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (4)

아주 간단하지만, 두번의 실행단계에 의해서 이루어지는 다음의 코드를 설명하려면 약간 복잡할 것이다.

그래도 한번 도전해보자!.

 

1. 아래 코드를 파싱과 실행 단계로 나누어서 순차적으로 설명하시오.

console.log(1, Person());
console.log(2, person);

var person = 1001;

var Person = function(age, height) {
    return age + “:” + height;
}

function Person() {
    console.log(SubPerson());
    var SubPerson = function() {
        return 10000;
    }
    function SubPerson() {
        return 20000;
    }
    return SubPerson();
}

person = Person(10,100);

console.log(3, person);
Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (3)

이번엔 좀더 자바스크립트의 내부를 살펴보도록 하겠다.

 

먼저 다음의 코드 부터 확인해보자.
아래 코드의 console.log()의 값은 1,2,3 각각 어떻게 나올까?

console.log(1, Person());
console.log(2, person);

var person = 1001;

function Person() {
    return 1000;
}

var Person = function(age, height) {
    return age + “:” + height;
}

person = Person(10,100);

console.log(3, person);

설명을 하면, 자바스크립트는 크게 두단계의 과정을 걸쳐서 프로그램을 실행시킨다.
이러한 이유는 자바스크립트는 약한타입언어이기 때문에, var라는 키워드로 모든 변수의 타입을 결정한다.
이말은 즉 런타임때 자료형이 결정된다는 의미이다. (여기서 자료형과 타입은 같은 말이다.)
그렇다면 어떤 과정이 강한타입언어와 다른지 살펴보자.

위에서 두단계과정을 걸쳐 실행한다고 했는데 첫번째가 파싱단계이다.
파싱단계는 자바스크립트의 코드중에서 var, function 두 키워드에 집중한다.
(WebStorm과 같은 IDE들을 보면 var, function 키워드만 다른 키워드와는 다른색으로 하이라이팅이 되어있다.)

이단계에서는 var 키워드로 만들어진 변수에 undefined를 할당하고, function 키워드로 만들어진 함수를 위해
메모리에 해당 함수의 로직을 올리고 Person이라는 변수에 주소를 할당한다. (특별히 = 연산자 뒤에 function은 무시한다.)

[파싱단계] *주소는 예시이다.

1. person = undefeind (0x111)

2. 0x123 = “function{return 1000;}”

3. Person = 0x123

4. Person = undefined(할당안함) => 그대로 0x123

4번 부분을 보자. 원래대로라면 undefined가 된다고 생각하겠지만 function 키워드가 var 키워드보다 우선순위가 높다는 것을 기억해두자.

다음은 실행단계이다.
이 단계에서는 실제 var, function키워드를 가지고 했던 파싱단계에서 수행했던 위 4단계를 제외하고 남은 모든 코드를 실제 수행한다.
로그 1번에서는 Person()를 수행한다.

[실행단계]

console.log(1, Person());
console.log(2, person);

var person = 1001;

function Person() {
    return 1000;
}

var Person = function(age, height) {
    return age + “:” + height;
}

person = Person(10,100);

console.log(3, person);

1. console.log(1, Person());
이부분은 Person이라는 0x123에 접근한뒤 () 연산자를 통해서 함수를 실행시킨다.
그러면 “function{ return 1000; }” 이라는 코드가 위에서 말한 파싱과 실행단계를 다시한번 거치고,
1000을 반환한다. 따라서 console.log(1, 1000)과 같게된다.
(여기서 유추할수 있는건 파싱과 실행단계는 함수단위로 이루어진다. 그렇다면 실제 아무것도 없는 상태에서
코드를 작성했단 것은 아무것도 없는 것도 무언가의 함수스코프 영역내 라는 의미이다.)

2. console.log(2, person); 을보면 현재 person에는 undefined가 할당되어 있다. 따라서 console.log(2, undefined)와 같게 된다.

3. person = 1001; var 부분을 제외하고 person에 1001이 할당된다.

4. 함수코드를 메모리 어딘가에 올린다.
0x999 = “function(age, height) {
return age + “:” + height;
}”

5. Person = 0x999
var 부분을 제외하고 아래 코드가 수행된다. 즉 Person에 함수의 주소를 할당한다.

6. person = Person(10, 1000);
에서 먼저 Person(10, 1000)를 수행하면, 리턴되는 값인 문자열 “10:100″이 person에 할당된다.
즉 person = “10:100″; 과같다.

7. console.log(3, person)
이부분은 결국 console.log(3, “10:100″); 으로 된다.

Standard
■ Javascript

[자바스크립트] Javascript를 기초부터 시작해보자! (2)

앞서 간단한 자바스크립트 내용에 대해서 살펴보았다.

그럼 얼마나 공부가 되었나 스스로 문제를 풀어보길 바란다.

 

1. 리터럴

var a = 10;
var b = “abc”;
var c = 10.3;
“good literal”;

리터럴에대해서 설명하시오.

[객체리터럴]
아래 프로퍼티 표현을 객체 리터럴로 표현하시오.

[객체 프로퍼티 표현] *여기서는 객체 전체가 아닌 28, “172m” 두 값만 리터럴이 된다.
var obj = new Obj();
obj.age = 28;
obj.tall = “172m”;

[배열리터럴]
아래 배열표현을 리터럴표현으로 바꿔보시오.

[배열표현]
var arr = new Array();
arr.push(“nice”);
arr.push(1);
arr.push(3);

2. 변수는 생략한다.

3. 데이터 타입

데이터 타입은 크게 두가지 범주로 나뉜다.

첫번째는 프리미티브(원시) 타입, 그리고 그외로 나눌 수 있다.
나누게 되는 기준은 (                                                               )이다.

프리미티브타입은 다시 크게 5가지로 나뉜다.

프리미티브타입 5개를 쓰고 설명하시오.

그외(                                                                    ) 등이 있다.

4. var
var는 모든 변수를 만들기 위한 키워드이다.
데이터 타입이 정해져 있지 않기 때문에, 약한타입언어라고 한다.
이부분 때문에 일반 강한타입형언어와는 다른 특색들이 나타난다. 아래서 다시 알아보자.

5. 값 타입과 포인터(참조) 타입

두 타입에 대해서 설명하시오.

Standard