이전에 프로토타입으로 작성한 프로젝트를 모놀리스에서 MSA로 리팩터링 하려고 했습니다. 이를 위해 ORM을 비교하던 중 Dapper와 EF를 두고 고민했는데 고려한 내용을 정리해 둡니다.

 

 

 

주요 차이점.

 

가장 큰 비교 요인으로 꼽히는 것은 성능입니다. Dapper는 EF에 비해 많은 기능을 희생했지만 그만큼 더 빠르다고 알려져 있습니다. 반대로 EF는 성능을 희생시키고 Dapper에서 지원하지 않는 어려운 기능들을 수행해 냅니다.

 

EF를 채택하는 주요한 기능은 Linq입니다. Linq로 인해 런타임에 SQL로 변환됩니다. EF의 Linq 지원은 실제로 IMO 채택을 주도한 주요한 요인입니다.

 

Dapper는 간단하면서 강력합니다. 그야말로 Simple is fast를 추구하는 것 같았습니다. EF에 비하면 초라한 기능이지만 성능면에서는 뛰어납니다. 

 

반면에 EF는 Linq를 지원하고 변경 감지 기능이 있어 dbContext.SaveChanges()를 통해 모든 업데이트를 한 번에 처리할 수 있습니다. 상속도 내장되어 있고 분할 쿼리, 전역 쿼리 필터, 전체 트랜잭션 관리, 마이그레이션 등 수많은 기능을 포함하고 있습니다.

 

Dapper는 개발 시 개발자가 raw 쿼리를 직접 작성해야 합니다. 이는 단점으로 보일 수 있지만 장점으로 작용할 수 도 있습니다.

 

 

 

EF의 성능 개선.

 

MS에 따르면 EF 7에서 데이터베이스에 대한 왕복 횟수를 줄이고 보다 효율적인 SQL을 생성하도록 변경되어 SaveChanges와 SaveChangesAsync의 성능이 일부 시나리오에서 EF 6에 비해 4배 빨라졌다고 게시하였습니다.

 

EF 6.0의 글에선 EF 6의 성능은 업계 표준 TechEmpower Fortunes 벤치마크에서 5.0에 비해 70% 더 빠르다고 게시한 바 있으며 벤치마크 코드, dotNET 런타임 등의 개선을 포함하여 전체 성능이 개선되어 EF Core 6.0 자체는 쿼리 실행 속도가 31% 더 빨라졌다고 게시한 바 있습니다.

 

이렇듯 EF는 기존에 Dapper에 비해 성능이 좋지 않아 지속적으로 성능을 개선시켜 나아가고 있습니다. 추후에는 더 나은 성능 개선을 바랄 수 있을 것으로 생각됩니다.

 

 

 

Dapper의 추가 라이브러리.

 

Dapper는 성능을 위해 많은 기능을 희생했습니다. 하지만 그렇다고 희생한 기능을 사용하지 못하는 것은 아닙니다. Dapper는 새로운 기능을 제공하는 확장 라이브러리가 있습니다. 

 

이러한 라이브러리들은 Dapper에서 기본적으로 제공하지 않는 기능을 지원해 유용하게 사용할 수 있으나 다른 라이브러리를 사용하면 Dapper에서 제공한 성능 벤치마크의 결과가 바뀔 수 있음을 유념해야 합니다.

 

 

 

결론.

 

다양한 레퍼런스를 찾아본 결과 결국 EF 7을 사용하기로 결정했습니다.

 

공식적으로 MS에서는 EF를 지원하는 것도 한몫했습니다. EF는 앞으로도 dotNet 버전과 함께 개선될게 분명하며 시간이 지나 신규 버전이 나올수록 개선되는 성능과 최신 벤치마크 테스트 결과 눈에 띄게 차이 나지 않는 성능으로 인해 딱히 Dapper를 사용해야 할 필요성을 느끼지 못했습니다.

 

다만 수많은 기능과 빠르게 변하는 만큼 러닝커브는 감내해야 할 사항입니다.

 

 

 

 

 

참조:

Is Dapper better than Entity Framework?

Using Entity Framework Core and Dapper in ASP.NET Core – Safe Transactions

Microsoft Claims Entity Framework Core 7 Faster When Saving Changes

The Big Fight — Dapper vs Entity Framework 6 Detailed Benchmark

Announcing Entity Framework Core 6.0 Preview 4: Performance Edition

Entity Framework Core 7 (EF7) is available today

 

 

반응형

맥에서 git명령어 수행 시 발생하는 "xcrun: error: invalid active developer path..." 에러를 해결하는 방법에 대해 알아봅니다.

 

 

 

현상.

 

 

Mac에서 git 명령어를 수행했는데 위와 같이 "xcrun: error: invalid active developer path..." 에러가 발생합니다.

 

 

 

원인.

 

검색 결과 MacOS에서 CommandLineTools을 인식하지 못해 발생한 문제라고 합니다.

 

 

 

수정.

 

다음 명령어로 CommandLineTools를 설치합니다.

 

xcode-select --install

 

 

그러면 위와 같이 확인 창이 팝업 되고 설치 버튼을 클릭해 설치를 진행해 주시면 됩니다.

 

 

설치가 완료된 후 명령어를 수행하면 정상적으로 명령어가 수행되는 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

C++에서 데이터를 Base Encoding/Decoding 하는 방법에 대해 알아봅니다.

 

 

 

1. base64 코드 작성

 

해당 코드는 다음 사이트를 참조하였습니다: Encoding and decoding base 64 with c++

 

Encoding and decoding base 64 with c++

This is the proposed interface for the version 2.0 of this library (as of 2020-04-29). base64_encode_pem / base64_encode_mime std::string base64_encode_pem (std::string const& s); std::string base64_encode_mime(std::string const& s); These two functions al

renenyffenegger.ch

 

//
//  base64 encoding and decoding with C++.
//  Version: 2.rc.09 (release candidate)
//

#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A
#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A

#include <string>

#if __cplusplus >= 201703L
#include <string_view>
#endif  // __cplusplus >= 201703L

std::string base64_encode     (std::string const& s, bool url = false);
std::string base64_encode_pem (std::string const& s);
std::string base64_encode_mime(std::string const& s);

std::string base64_decode(std::string const& s, bool remove_linebreaks = false);
std::string base64_encode(unsigned char const*, size_t len, bool url = false);

#if __cplusplus >= 201703L
//
// Interface with std::string_view rather than const std::string&
// Requires C++17
// Provided by Yannic Bonenberger (https://github.com/Yannic)
//
std::string base64_encode     (std::string_view s, bool url = false);
std::string base64_encode_pem (std::string_view s);
std::string base64_encode_mime(std::string_view s);

std::string base64_decode(std::string_view s, bool remove_linebreaks = false);
#endif  // __cplusplus >= 201703L

#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */

 

/*
   base64.cpp and base64.h

   base64 encoding and decoding with C++.
   More information at
     https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp

   Version: 2.rc.09 (release candidate)

   Copyright (C) 2004-2017, 2020-2022 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch

*/

#include "base64.h"

#include <algorithm>
#include <stdexcept>

 //
 // Depending on the url parameter in base64_chars, one of
 // two sets of base64 characters needs to be chosen.
 // They differ in their last two characters.
 //
static const char* base64_chars[2] = {
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789"
             "+/",

             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789"
             "-_"};

static unsigned int pos_of_char(const unsigned char chr) {
 //
 // Return the position of chr within base64_encode()
 //

    if      (chr >= 'A' && chr <= 'Z') return chr - 'A';
    else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A')               + 1;
    else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
    else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters (
    else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_'
    else
 //
 // 2020-10-23: Throw std::exception rather than const char*
 //(Pablo Martin-Gomez, https://github.com/Bouska)
 //
    throw std::runtime_error("Input is not valid base64-encoded data.");
}

static std::string insert_linebreaks(std::string str, size_t distance) {
 //
 // Provided by https://github.com/JomaCorpFX, adapted by me.
 //
    if (!str.length()) {
        return "";
    }

    size_t pos = distance;

    while (pos < str.size()) {
        str.insert(pos, "\n");
        pos += distance + 1;
    }

    return str;
}

template <typename String, unsigned int line_length>
static std::string encode_with_line_breaks(String s) {
  return insert_linebreaks(base64_encode(s, false), line_length);
}

template <typename String>
static std::string encode_pem(String s) {
  return encode_with_line_breaks<String, 64>(s);
}

template <typename String>
static std::string encode_mime(String s) {
  return encode_with_line_breaks<String, 76>(s);
}

template <typename String>
static std::string encode(String s, bool url) {
  return base64_encode(reinterpret_cast<const unsigned char*>(s.data()), s.length(), url);
}

std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) {

    size_t len_encoded = (in_len +2) / 3 * 4;

    unsigned char trailing_char = url ? '.' : '=';

 //
 // Choose set of base64 characters. They differ
 // for the last two positions, depending on the url
 // parameter.
 // A bool (as is the parameter url) is guaranteed
 // to evaluate to either 0 or 1 in C++ therefore,
 // the correct character set is chosen by subscripting
 // base64_chars with url.
 //
    const char* base64_chars_ = base64_chars[url];

    std::string ret;
    ret.reserve(len_encoded);

    unsigned int pos = 0;

    while (pos < in_len) {
        ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]);

        if (pos+1 < in_len) {
           ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]);

           if (pos+2 < in_len) {
              ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]);
              ret.push_back(base64_chars_[  bytes_to_encode[pos + 2] & 0x3f]);
           }
           else {
              ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]);
              ret.push_back(trailing_char);
           }
        }
        else {

            ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]);
            ret.push_back(trailing_char);
            ret.push_back(trailing_char);
        }

        pos += 3;
    }


    return ret;
}

template <typename String>
static std::string decode(String const& encoded_string, bool remove_linebreaks) {
 //
 // decode(…) is templated so that it can be used with String = const std::string&
 // or std::string_view (requires at least C++17)
 //

    if (encoded_string.empty()) return std::string();

    if (remove_linebreaks) {

       std::string copy(encoded_string);

       copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end());

       return base64_decode(copy, false);
    }

    size_t length_of_string = encoded_string.length();
    size_t pos = 0;

 //
 // The approximate length (bytes) of the decoded string might be one or
 // two bytes smaller, depending on the amount of trailing equal signs
 // in the encoded string. This approximation is needed to reserve
 // enough space in the string to be returned.
 //
    size_t approx_length_of_decoded_string = length_of_string / 4 * 3;
    std::string ret;
    ret.reserve(approx_length_of_decoded_string);

    while (pos < length_of_string) {
    //
    // Iterate over encoded input string in chunks. The size of all
    // chunks except the last one is 4 bytes.
    //
    // The last chunk might be padded with equal signs or dots
    // in order to make it 4 bytes in size as well, but this
    // is not required as per RFC 2045.
    //
    // All chunks except the last one produce three output bytes.
    //
    // The last chunk produces at least one and up to three bytes.
    //

       size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos+1) );

    //
    // Emit the first output byte that is produced in each chunk:
    //
       ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char(encoded_string.at(pos+0)) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4)));

       if ( ( pos + 2 < length_of_string  )       &&  // Check for data that is not padded with equal signs (which is allowed by RFC 2045)
              encoded_string.at(pos+2) != '='     &&
              encoded_string.at(pos+2) != '.'         // accept URL-safe base 64 strings, too, so check for '.' also.
          )
       {
       //
       // Emit a chunk's second byte (which might not be produced in the last chunk).
       //
          unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos+2) );
          ret.push_back(static_cast<std::string::value_type>( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2)));

          if ( ( pos + 3 < length_of_string )     &&
                 encoded_string.at(pos+3) != '='  &&
                 encoded_string.at(pos+3) != '.'
             )
          {
          //
          // Emit a chunk's third byte (which might not be produced in the last chunk).
          //
             ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string.at(pos+3))   ));
          }
       }

       pos += 4;
    }

    return ret;
}

std::string base64_decode(std::string const& s, bool remove_linebreaks) {
   return decode(s, remove_linebreaks);
}

std::string base64_encode(std::string const& s, bool url) {
   return encode(s, url);
}

std::string base64_encode_pem (std::string const& s) {
   return encode_pem(s);
}

std::string base64_encode_mime(std::string const& s) {
   return encode_mime(s);
}

#if __cplusplus >= 201703L
//
// Interface with std::string_view rather than const std::string&
// Requires C++17
// Provided by Yannic Bonenberger (https://github.com/Yannic)
//

std::string base64_encode(std::string_view s, bool url) {
   return encode(s, url);
}

std::string base64_encode_pem(std::string_view s) {
   return encode_pem(s);
}

std::string base64_encode_mime(std::string_view s) {
   return encode_mime(s);
}

std::string base64_decode(std::string_view s, bool remove_linebreaks) {
   return decode(s, remove_linebreaks);
}

#endif  // __cplusplus >= 201703L

 

 

 

2. 테스트

 

다음과 같이 테스트 코드를 작성합니다

 

#include <iostream>
#include "base64.h"

int main()
{
    std::string inputString = "Hello World!";
    std::string encoded = base64_encode(reinterpret_cast<const unsigned char*>(inputString.c_str()), inputString.length());
    std::cout << "encoded string: [" << encoded << "]\n";
    std::string decoded = base64_decode(encoded);
    std::cout << "decoded string: [" << decoded << "]\n";
}

 

콘솔에서 확인하면 다음과 같이 정상적으로 출력됩니다.

 

 

 

 

 

 

반응형

 

C#의 GZipStream으로 압축된 String 데이터를 C++ 프로그램에서 다룰 일이 생겼습니다. 이를 위한 라이브러리 검토 중 POCO C++를 사용해 테스트를 진행해보도록 하겠습니다.

 

 

 

1. POCO C++ 다운로드

 

vcpkg를 사용해 라이브러리를 다운로드합니다. vcpkg 설치는 다음 글을 참조해 설치해 주시기 바랍니다: C++ 라이브러리를 위한 vcpkg 설치 방법

 

C++ 라이브러리를 위한 vcpkg 설치 방법

Window 환경에서 C++ 라이브러리를 위한 vcpkg를 설치하는 방법에 대해 알아봅니다. vcpkg 설치. 가장 쉬운 방법은 공식 홈페이지의 안내를 따르는 방법입니다. git을 미리 설치했다면 두줄의 명령줄로

smoh.kr

 

vcpkg가 설치된 폴더로 이동해 다음 명령어로 POCO C++를 다운로드합니다.

 

.\vcpkg install POCO:x64-windows

 

 

잠시 기다리면 다운로드와 빌드가 완료되고 라이브러리를 확인할 수 있습니다.

 

 

 

 

2. 프로젝트 생성

 

간단한 C++ 콘솔 프로젝트를 생성합니다. 이후 다운로드한 라이브러리를 import 합니다.

 

 

 

이제 "Hello World"를 gzip 압축 및 압축 해제하는 코드를 작성합니다.

 

 

 

3. 코드 작성

 

다음과 같이 코드를 작성합니다.

 

#include <iostream>
#include <sstream>
#include <Poco/InflatingStream.h>
#include <Poco/DeflatingStream.h>
#include <Poco/StreamCopier.h>

int main()
{
    std::ostringstream stream1;
    Poco::DeflatingOutputStream
        gzipper(stream1, Poco::DeflatingStreamBuf::STREAM_GZIP);
    gzipper << "Hello World!";
    gzipper.close();
    std::string zipped_string = stream1.str();
    std::cout << "zipped_string: [" << zipped_string << "]\n";

    std::ostringstream stream2;
    Poco::InflatingOutputStream
        gunzipper(stream2, Poco::InflatingStreamBuf::STREAM_GZIP);
    gunzipper << zipped_string;
    gunzipper.close();
    std::string unzipped_string = stream2.str();
    std::cout << "unzipped_string back: [" << unzipped_string << "]\n";
}

 

"Hello World!"를 GZIP 압축 및 압축 해제하는 코드입니다. 콘솔로 실행시키면 다음과 같이 정상 동작하는 것을 확인할 수 있습니다.

 

 

 

 

 

 

반응형

 

Window 환경에서 C++ 라이브러리를 위한 vcpkg를 설치하는 방법에 대해 알아봅니다.

 

 

 

vcpkg 설치.

 

가장 쉬운 방법은 공식 홈페이지의 안내를 따르는 방법입니다. git을 미리 설치했다면 두줄의 명령줄로 설치를 완료할 수 있습니다: Get started with vcpkg

 

Get started with vcpkg

Installing vcpkg is a two-step process: first, clone the repo, then run the bootstrapping script to produce the vcpkg binary. The repo can be cloned anywhere, and will include the vcpkg binary after bootstrapping as well as any libraries that are installed

vcpkg.io

 

공식 홈페이지에서는 전역으로 설치하는 경우 C:\src\vcpkg 또는 C:\dev\vcpkg와 같은 짧은 설치 경로를 권장하고 있습니다.

 

터미널(혹은 명령 창)을 열어 원하는 경로로 이동 한 뒤 다음 명령어를 실행합니다.

 

$git clone https://github.com/Microsoft/vcpkg.git

 

잠시 기다리면 다음과 같이 다운로드가 완료됩니다.

 

 

이어서 다음 명령어를 실행해 줍니다.

 

 

.\vcpkg\bootstrap-vcpkg.bat

 

다음 화면과 같이 정상적으로 처리되는 것을 확인할 수 있습니다.

 

 

만약 VisualStudio에서 vcpkg를 사용하고자 한다면 추가로 다음 명령어를 수행해야 합니다.

 

.\vcpkg\vcpkg integrate install

 

관리자 권한이 요구되며 다음과 같이 완료됩니다.

 

 

 

 

라이브러리 설치

 

가장 대표적인 C++ 라이브러리인 boost를 vcpkg를 사용해 설치해 봅시다. 다음 명령어를 입력해 boost(x64)를 설치합니다. 

** boost는 매우 방대한 라이브러리입니다. 상당히 오랜 시간이 소요되니 테스트 용도라면 다른 라이브러리로 테스트하셔도 됩니다.

 

.\vcpkg\vcpkg install boost:x64-windows

 

위와 같이 실행하면 64비트로 빌드된 boost 관련 라이브러리를 모두 다운로드하게 됩니다.

 

 

이제 기다리면 리스트업 된 모든 라이브러리를 다운로드하게 되고 명령어를 통해 다운로드한 라이브러리를 확인할 수 있습니다.

 

 

.\vcpkg\vcpkg list

 

 

라이브러리 파일은 다음 경로에서 확인할 수 있습니다.

 

 

정상적으로 라이브러리가 다운로드된 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

https://www.akana.com/blog/what-is-an-api-gateway

 

앞선 글

 

API 게이트웨이의 개념에 대해서는 어느 정도 알고 있었지만 정확이 어떤 개념으로, 어떤 방식으로 구현되고 어떤 구조로 설계되는지 실제 개발에 앞서 먼저 알아보고자 이 글을 작성합니다.

 

이 글은 다음 글을 번역한 글입니다: The Concept Of An API Gateway

 

What is an API Gateway? How Does it Work? ⚙️

API gateway is a passage that acts as a connector for 2 components to make them achieve certain functionality. Why use an API Gateway? API Gateway security

www.wallarm.com

 

해당 글을 통해 좀 더 자세한 정보를 이해 한 뒤 Ocelot을 사용해 API 게이트웨이를 구현하는 글까지 작성할 예정입니다. 

 

 

 

서론

 

일반적으로 게이트웨이는 두 개의 컴포넌트가 특정 기능을 수행할 수 있도록 하는 커넥터 역할을 하는 통로입니다. API 게이트웨이도 크게 다르지 않습니다. 이것은 우리들이 이해해야 할 중요한 주제입니다.

 

 

 

API 게이트웨이가 무엇인지 빠르게 알아보기.

 

API 게이트웨이는 API와 다양한 백엔드 서비스 사이에 배치된 가상의 통로와 같습니다. 요청과 호출 처리에 적합한 스테이션이나 서비스와 매치시키고 대상 리소스로 다시 요청을 보냅니다.

 

기업과 데이터를 중심으로 하는 조직을 위한 API에는 추가 보안 계층, 액세스 모니터링, 사용량 제한과 같은 기능이 필요합니다. 게이트웨이는 요청 횟수, 데이터 사용량, 요청 소스 유효성 검사, 액세스 및 사용자 인증을 처리해 추가 기능을 제공합니다.

 

 

API 게이트웨이의 아이디어는 규칙 지향적인 리소스 표준을 중심으로 여러 개의 비조정 서비스가 중앙 집중식 통신 생태계를 공유할 수 있도록 합니다. 

 

 

 

왜 API 게이트웨이를 사용할까?

 

초보자에게는 API 호출을 적절하게 감동하는 이상적인 수단으로만 보일 수 있습니다. 하지만 API 게이트웨이는 더 많은 기능을 수행합니다. 

  • 인증, 속도 제한과 같은 기능으로 API 남용을 검사하는데 도움을 줍니다.
  • 개발자가 API가 사용되는 방법에 대한 다양한 시나리오를 찾는데 도움을 줍니다.
  • 수익을 창출하는 설루션의 경우 백엔드 프로세스와 청구 시스템 간의 원활한 관계를 구성하도록 합니다.
  • 마이크로 서비스 배포의 경우 다양한 애플리케이션에 대한 특성 요청을 구성하도록 합니다.
  • 업데이트가 발생하는 경우에도 API 유지 관리, 업그레이드, 현대화에 필요한 모든 리소스를 확보하는데 도움이 됩니다.
  • API 게이트웨이는 API에 발생하는 모든 것을 관찰할 수 있으므로 여러 개의 API 처리에 도움을 줍니다.

 

 

 

API 게이트웨이는 어떻게 동작할까?

 

애플리케이션 테스팅과 사용에는 수많은 데이터 교환 작업이 포함됩니다. 이러한 유형의 통신에는 사전 준비가 필요합니다. 문제들을 분류하기 위해서 API 게이트웨이는 다양한 요청을 수신할 수 있는 중앙 플랫폼으로써의 역할을 수행합니다.

 

이 과정에서 여러 API 호출이 모이고 인증되어 적절한 API로 리디렉션 됩니다.

 

마이크로 서비스 생태계에서 특정 마이크로 서비스의 요청에 대한 적절한 진입을 만들어 냅니다. 또한 접근성과 수행 기준을 설정합니다.

 

 

게다가 API 게이트웨이는 서비스 검색, API 프로토콜 변환, 비즈니스 로직 처리, 캐시 관리, 네트워크 트래픽 지원, API 모니터링과 같은 기능을 다룹니다.

 

 

 

전체 API 관리에서 게이트웨이의 중요성.

 

API 호출을 처리하고 담당하는 곳으로 분산시킬 수 있다는 것은 API 게이트웨이를 API 관리에서 없어서는 안 될 존재로 만듭니다. 여기에서 비율 제한, 알림, 분석, 인증, 정책, 비용 계산, 안정성과 같은 기능을 수행합니다.

 

 

 

마이크로 서비스 아키텍처를 위한 API 게이트웨이.

 

수많은 작은 컴포넌트들이 마이크로 서비스를 구성합니다. 이러한 접근 방식은 개발자가 사용자 경험을 향상하는데 도움이 됩니다. 여기에는 API 게이트웨이가 반드시 필요합니다. 결국 API 게이트웨이는 이러한 컴포넌트에 대한 번역기와 같은 역할을 하여 신속하고 오류 없는 API 구현을 보장합니다.

 

 

일반적으로 게이트웨이는 최대한의 클라이언트 요청을 처리하고 모든 요청을 중앙 집중식으로 유지하고 결합할 수 있습니다. 이렇게 하면 클라이언트와 응용프로그램 간의 통신에 소요되는 시간이 줄어들어 운영 비용도 절감할 수 있게 됩니다.

 

 

 

API 게이트웨이를 사용하여 얻는 이점.

 

게이트웨이를 배치하는 것은 API 개발에서부터 관리, 운영 비용에 이르기까지 모든 것이 한 번에 처리되므로 엔드유저, 애플리케이션, API, 설루션 개발자 모두에게 좋습니다.

 

효율적인 API 개발.

API게이트웨이는 특정 API의 다양한 버전을 실행하고 가능한 한 최소한의 노력으로 API를 테스트, 반복, 업데이트할 수 있는 기능을 개발자에게 제공합니다. API 개발은 빠르고 똑같이 효율적이 됩니다. API 게이트웨이는 API 호출과 데이터 전송에 대해서만 책임을 지므로 이행해야 하는 최소한의 책무는 없습니다.

 

모든 규모에 대한 생산성.

API 게이트웨이는 개발자가 최소한의 지연시간으로 작업을 가능하게 하여 엔드 유저에게 더 나은 경험을 제공할 수 있도록 지원합니다. 또한 트래픽 제한과 API 요청에 대한 인증 기능을 가능하게 합니다. 이 두 기능은 백엔드 API 개발 팀이 트래픽 급증을 처리하고 지속적인 API 성능을 보장하는데 도움이 됩니다.

 

간편한 모니터링.

API 게이트웨이는 모든 것을 통합된 지점으로 가져오고 API 호출 정보, 오류, 지연과 관련된 세부 정보와 같은 데이터 통합에 대한 가시성을 제공해 줍니다. 모든 측정 항목에 즉시 접근할 수 있으므로 개발자는 모든 단계에서 API 성능을 추적하고 숨겨진 경고를 찾을 수 있습니다.

 

모든 규모에 대한 비용 절감.

API 게이트웨이는 여러 구독 옵션과 함께 제공되며 API 요청에 따라 패키지를 자유롭게 선택할 수 있습니다. 일반적으로 0.9 달러의 비용으로 백만 개의 API 요청을 처리할 수 있습니다. 이러한 유연한 가격 정책으로 API 개발 비용을 통제할 수 있게 합니다.

 

 

 

API 게이트웨이 사용의 문제점.

 

안전성.

API 호출에서 보안 관행을 구현하고 성능을 추적하는 것은 어렵습니다. 이 작업에는 처리해야 할 다양한 작은 요청이 존재하므로 마이크로 서비스 생태계의 탭이 됩니다. 또한 내부와 외부 API에 대한 다양한 안전 관행을 생각해야 합니다. 이것은 훨씬 더 많은 작업을 의미합니다.

 

지속 가능성과 신뢰성.

API 게이트웨이가 여러 요청을 결합하게 되므로 만약 게이트웨이가 동작하지 않으면 전체 API 인프라가 붕괴됩니다. 

 

복잡성을 유발하는 높은 수준의 종속성.

효과적인 작업을 위해서는 모든 마이크로 서비스를 추가해 API 게이트 업데이트를 유지 관리하는 것이 중요합니다. 단일 애플리케이션이 수백만 개의 마이크로 서비스로 바뀌면 너무나 많은 일이 발생합니다. 이러한 시나리오에서 게이트웨이를 업데이트하는 것은 끝나지 않는 작업이 될 수도 있습니다.

 

 

 

Service Mash vs. API 게이트웨이

 

제어의 중앙 집중화를 촉진하는 API 게이트웨이와 달리 서비스 매쉬는 다양한 마이크로 서비스를 이용해 애플리케이션에서 고유한 기능을 수행하도록 합니다. 서비스 매쉬에서 마이크로 서비스 간의 교환을 중재하기 위해 API 게이트웨이를 사용하면 보안 수준과 작업 실행 속도가 개선될 수 있습니다.

 

목적

API 게이트웨이는 조직, DB, 외부의 호출이나 요청을 안전하게 라우팅하고 서비스 매쉬는 마이크로 서비스를 사용해 조직 네트워크 내에서 플랫폼 독립성을 향상합니다.

 

동작

API 게이트웨이는 외부 네트워크와 상호작용하고 조직 네트워크에서 요청을 전달할 수 있습니다. 서비스 매쉬는 조직 내 네트워크 내부에서 동작합니다.

 

책임.

API 게이트웨이는 네트워크 API의 관리와 안전에 대한 책임을 갖습니다. 서비스 매쉬는 시스템과 네트워크의 성능과 휴대성을 향상하며 서비스 매쉬에서 API는 외부로부터 서비스 매쉬를 숨기고 보호합니다.

 

보안 메서드.

API 게이트웨이에서는 자동화와 정책을 사용할 수 있습니다. 서비스 매쉬에서는 보안 전략을 수동으로 구현해야 합니다.

 

 

 

맺는 글

 

이상으로 간단하게 API 게이트웨이가 무엇인지, 어떻게 구성되는지, 이점이 무엇인지에 대해 알아보았습니다. 이 글이 API 게이트웨이의 개념을 이해하는데 도움이 되셨길 바랍니다.

 

 

 

 

 

 

반응형

 

Blazor 호스팅 모델은 두 가지가 있습니다. 하나는 Blazor Server고 다른 하나는 Blazor WebAssembly입니다. 이 두 호스팅 모델의 차이점에 대해 알아봅니다.

 

 

 

Blazor Server vs Blaser WebAssembly

 

Blazor 서버는 2019년 9월에 dotNet Core 3.0과 함께 출시되었습니다. Blazor WebAssembly는 2020년 5월에 출시되었습니다.

 

이 두 호스팅 모델의 주요한 차이점은 dotNet 코드가 실행되는 위치입니다. Blazor Server의 경우 100% 서버 측에서 실행횝니다. 호스팅 된 Blazor WASM 애플리케이션을 사용하면 dotNet 코드가 서버와 클라이언트 모두에서 실행됩니다. Blazor WASM의 경우 서버 측에서 dotNet이 아닌 원하는 다른 언어를 사용할 수도 있습니다.

 

Blazor Server와 Blazor WebAssembly 모두 서버 측 코드를 갖고 있습니다. 하지만 두 호스팅 모델에서 서버 측 코드의 역할이 다릅니다.

 

Blazor Server의 경우 dotNet 런타임과 함께 100% 서버 측이며 프레임워크 JavaScript 라이브러리는 클라이언트에서 서버와 통신하는 데 사용됩니다. 따라서 Blazor Server를 사용하면 하나의 애플리케이션을 얻게 됩니다.

 

Blazor WASM을 사용하면 클라이언트가 브라우저에서 별도로 dotNet 런타임을 실행합니다. 호스트 된 모델은 별도의 dotNet Web API Server 프로젝트를 생성하지만 클라이언트는 기술에 구애받지 않습니다. 따라서 모든 백엔드를 사용할 수 있게 됩니다.

 

 

 

SignalR

 

SignalR은 Blazor Server에선 서버와 클라이언트가 지속적으로 통신하고 업데이트하기 위해 필요할 수 있습니다.

 

하지만 Blazor WASM에서는 좀 더 유연한 선택지가 있습니다. 이 문서에 따르면 호스팅 된 클라이언트 앱은 웹 API, gRPC-web 및 SignalR과 같은 다양한 메시징 프레임워크 및 프로토콜을 사용하여 네트워크를 통해 백엔드 서버 앱과 상호 작용할 수 있습니다.

 

Blazor WASM은 서버 측 입장에선 실체를 알 수 없습니다. 호스팅 된 모델은 서버 측을 생성하긴 하지만 기술적으로 원하는 아무것이든 사용할 수 있습니다. 

 

정리하자면 Blazor 서버에는 Signal R이 필요하지만 Blazor WASM은 기술에 구애받지 않습니다. Signal R을 사용하도록 만들 수 있지만 표준 HTTP 프로토콜만 있으면 됩니다.

 

 

 

Deploy

 

Blazor Server는 클라이언트 측을 컴파일하지 않습니다. 일단 애플리케이션에 연결되면 Signal R을 활용하여 웹 소켓 또는 다른 기술을 통해 클라이언트에 업데이트를 지속적으로 푸시합니다.

 

Blazor Server의 Publish 결과물

 

Blazor WASM은 클라이언트 측입니다. WASM 프로젝트를 컴파일할 때 React 애플리케이션에 대해 Webpack을 실행하는 것과 유사한 결과를 얻게 됩니다.

 

Blazor WASM의 Publish 결과물

 

Blazor WASM은 프런트엔드 기술이므로 정적 웹 페이지의 종속성으로 제공되거나 호스팅 된 모델과 같이 웹 API에 의해 확장 및 제공될 수 있습니다.

 

 

 

Pros and Cons

 

Blazor Server의 장점

  • 다운로드 크기는 Blazor WebAssembly 앱보다 훨씬 작으며 앱이 훨씬 빠르게 로드되며 앱은 dotNET Core API 사용을 포함하여 서버 기능을 최대한 활용합니다.
  • 서버의 dotNET Core는 앱을 실행하는 데 사용되므로 디버깅과 같은 기존 dotNET 도구가 예상대로 작동합니다.
  • 씬 클라이언트가 지원됩니다. 예를 들어 Blazor Server 앱은 WebAssembly를 지원하지 않는 브라우저 및 리소스가 제한된 장치에서 작동합니다.
  • 데이터베이스 또는 클라우드 기반 서비스와 같은 보안 리소스에 대한 액세스가 가능합니다.
  • 앱의 구성 요소 코드를 포함한 앱의 dotNET/C# 코드 베이스는 클라이언트에 제공되지 않습니다.
  • 신뢰할 수 있는 환경에서 모든 처리 코드를 실행하며 공개 API가 필요하지 않습니다.

Blazor Server의 단점

  • 일반적으로 더 긴 대기 시간이 존재합니다. 모든 사용자 상호 작용에는 네트워크 홉이 포함됩니다.
  • 오프라인 지원이 없습니다. 클라이언트 연결에 실패하면 앱이 작동을 멈춥니다.
  • 사용자가 많은 앱을 확장하려면 여러 클라이언트 연결 및 클라이언트 상태를 처리하기 위한 서버 리소스가 필요합니다.
  • 앱을 제공하려면 ASP.NET Core 서버가 필요합니다. CDN(콘텐츠 전송 네트워크)에서 앱을 제공하는 것과 같은 서버리스 배포 시나리오는 불가능합니다.

 

Blazor WASM의 장점

  • 앱이 서버에서 다운로드된 후에는 dotNET 서버 쪽 종속성이 없으므로 서버가 오프라인 상태가 되어도 앱은 계속 작동합니다.
  • 클라이언트 리소스와 기능이 완전히 활용됩니다.
  • 작업이 서버에서 클라이언트로 오프로드 됩니다.
  • 앱을 호스팅 하는 데 ASP.NET Core 웹 서버가 필요하지 않습니다. 이는 콘텐츠 전송 네트워크(CDN)에서 앱을 제공하는 것과 같은 서버리스 배포 시나리오가 가능하게 합니다.

Blazor WASM의 단점

  • 앱은 브라우저의 기능으로 제한됩니다.
  • 가능한 클라이언트 하드웨어 및 소프트웨어(예: WebAssembly 지원)가 필요합니다.
  • 다운로드 크기가 더 크고 앱을 로드하는 데 시간이 더 오래 걸립니다.
  • 보안 리소스에 액세스 하려면 별도의 API 계층이 필요합니다.
  • 디버깅은 여전히 약간 제한적입니다.

 

 

 

References

 

 

 

 

 

 

반응형

 

 

 

0. 앞선 글.

 

언젠가 웹 프로젝트로 그럴듯한 페이지를 만들어보고 싶단 생각을 하곤 했습니다. 그를 위해 틈틈이 React에 대해 공부도 해보고 간단한 페이지도 만들어보고 했지만 이것저것 붙이는 순간 번번이 무너져 내리곤 했습니다.

 

진짜 React가 맞는가에 대한 고민을 하며 Vue와 Svlte에 대해서도 알아보던 와중 Blazor와 React를 비교한 글이 있어 해당 글을 읽고 번역을 해 남겨둡니다.

 

만약 여러분이 C#을 어느 정도 할 줄 알고 아직 웹 프레임워크를 선택하지 않았다면 이 글이 약간이나마 도움이 될 수 있기를 바랍니다.

 

이 글은 다음 글을 번역한 글입니다: React vs. Blazor: Minimize JavaScript in your SPAs

 

React vs. Blazor: Minimize JavaScript in your SPAs - LogRocket Blog

Explore React and Blazor, two different client-side options for minimizing JavaScript, comparing the two in terms of features.

blog.logrocket.com

 

 

 

1. SPA에서 JavaScript 최소화 하기.

 

SPA(단일 페이지 애플리케이션, Single Page Application)을 구축할 때 개발자가 선택할 수 있는 몇 가지 프레임워크가 있습니다. 가장 있기 있는 세 가지 프레임워크는 Angular, React, Vue입니다. 하지만 이 세 프레임워크를 사용해 SPA를 구축하려면 JavaScript가 필요합니다.

 

개발자가 SPA를 만드는데 관심이 있지만 JavaScript의 문제들을 처리하고 싶지 않다면 어떻게 해야 할까요? 이 글에서는 JavaScript를 최소화하기 위한 두 가지 다른 클라이언트 측(client-side) 옵션인 React와 Blazor를 살펴보고 그 기능을 비교합니다. 이제 시작해 봅시다.

 

 

 

2. Blazor가 뭔가요?

 

Blazor는 C# dotNet과 WebAssembly를 활용하여 그 고유한 방식을 따라 웹 브라우저에서 실행되는 SPA를 만드는 Microsoft UI 프레임워크입니다. 기본적으로 Blazor를 사용하면 개발자가 HTML, CSS, C#을 사용해 대화형 클라이언트 측 애플리케이션을 빌드할 수 있습니다.

 

반면에 React는 유저 인터페이스와 UI 컴포넌트를 빌드하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다. React와 Blazor는 풍부하고 대화형을 지원하면서 현대적인 클라이언트 측 애플리케이션을 구축하기 위한 프레임워크와 라이브러리라는 유사성을 갖고 있습니다.

 

Blazor의 고유한 기능 중 하나는 JavaScript의 상호 운용성입니다. 즉 Blazor앱은 dotNet 메서드에서 JavaScript 함수를 호출하고 JavaScript 함수에서 dotNet 메서드를 호출할 수 있습니다.

 

Blazor는 클라이언트 측 Blazor WebAssembly와 서버 측 Blazor Server의 두 가지 주요 프로젝트로 구성됩니다. Blazor 서버 구현은 보다 전통적인 dotNet 앱 접근 방식을 사용하며 서버가 메시지 전달을 위해 SignalR을 사용해 프런트엔드로 연결이 필요합니다. 반면 Blazor WebAssembly는 프로젝트를 배포 가능한 정적 번들로 패키징 합니다. 이 글에서는 Blazor WebAssembly와 React를 비교하는데 초점을 둡니다.

 

 

 

3. Blazor Wasm(WebAssembly)

 

Blazor Wasm은 Xamarin Mono 프레임워크의 컴파일된 버전과 함께 실행됩니다. 최종 결과는 실제 브라우저에서 직접 dotNet C# 애플리케이션을 실행할 수 있습니다. Blazor는 dotNet 코드를 Wasm으로 컴파일 한 다음 정적 번들로 배포할 수 있습니다. 오래된 브라우저의 경우 Blazor는 asm.js를 사용해 브라우저가 지원하는 공통 프레임워크로 컴파일합니다.

 

Blazor는 JavaScript 라이브러리와 상호 운용성을 지원합니다. npm 모듈을 빌드와 함께 가져와 Blazor 프로젝트와 함께 사용할 수 있습니다. C# 코드와 JavaScript 패키지에 직접 상호작용 하기 위해 Blazor는 IJSRuntime in C# 및 IJSRuntime을 제공합니다. 자세한 설명은 Microsoft의 문서를 확인하시기 바랍니다.

 

 

 

4. 폴더 구조.

 

Blazor 앱과 React 앱을 모두 만들어보고 기본적으로 무엇이 있는지 비교해 보겠습니다. 새 Blazor 프로젝트를 만들려면 dotNet SDK를 다운로드해 설치하고 터미널에서 다음 명령어를 실행해야 합니다.

 

dotnet new blazorwasm -n BlazorApp

 

blazorwasm 명령은 WebAssembly로 새로운 Blazor 프로젝트를 생성한다는 의미입니다. -n 플래그는 프로젝트 이름을 의미합니다. 

 

Blazor Foler Structure

 

program.cs 파일에는 WebAssembly 앱을 시작하고 실행하는데 필요한 주요 메서드가 포함되어 있습니다.

 

program.cs

 

또한 앱을 마운트하고 앱 ID가 있는 태그를 선택합니다. HTTP 클라이언트는 종속성 주입을 사용해 로드되고 Blazor 앱의  HTTP 모듈은 JavaScript Fetch API를 기반으로 빌드됩니다.

 

React Folder Structure

 

React 앱에서 index.js는 Blazor 앱의 program.cs와 유사합니다. 아래 코드 스니펫에서 React DOM은 앱 구성요소를 렌더링하고 루트 구성요소로 ID가 root인 요소를 선택합니다.

 

index.js

 

 

 

5. 프로그래밍 언어.

 

Blazor는 JavaScript 대신 C#을 프로그래밍 언어로 사용해 dotNet 라이브러리의 기존 dorNet 에코시스템을 활용합니다. 이 기능을 통해 C# 개발자는 C#을 사용해 백엔드 코드를 작성하는 것을 시작으로 C#을 사용해 웹 및 모바일 애플리케이션을 구축하는 풀 스택까지 기술을 확장할 수 있습니다.

 

처음에는 이 C#이 JavaScript 개발자에게 자연스럽지 않을 수 있지만 C#을 잘 이해하면 완전히 C#으로 작성된 강력한 풀 스택 애플리케이션을 빌드할 수 있습니다.

 

React는 JavaScript를 프로그래밍 언어로 사용합니다. 기본적으로 웹 개발자는 가장 익숙한 언어를 사용할 수 있습니다. 또한 React Native를 사용하면 동일한 코드를 공유하는 웹, 안드로이드, iOS 애플리케이션을 구축할 수 있습니다.

 

 

 

6. 템플릿

 

React로 SPA를 생성할 때 Create React App 툴 체인 사용을 권장합니다. Create React App 툴 체인은 기본적으로 JavaScript용 구문 확장인 JSX로 구성된 React 앱으로 초기화합니다. JSX는 React에서 HTML을 작성하고 HTML로 JavaScript를 작성할 수 있게 해주는 템플릿 엔진처럼 동작합니다.

 

JSX

 

Blazor는 수년간 사용되어 온 Razor 템플릿 엔진을 사용합니다. 이 템플릿 엔진은 C#과 ASP.NET 에코시스템에서 새로운 것이 아닙니다. 이 엔진은 C#과 ASP.NET을 사용해 웹 페이지에 서버 측 코드를 포함하는 데 사용됩니다.

 

Razor 템플릿

 

JSX처럼 Razor 템플릿 엔진을 사용하면 마크업에 C# 코드를 작성할 수 있습니다.

 

 

 

7. 성능.

 

Blazer 프로젝트는 브라우저에서 필요한 DLL 라이브러리와 전체 dotNet 런타임을 다운로드해야 하므로 더 느립니다. 또한 Blazor 앱에는 대기시간에 문제가 있습니다. 따라서 전 세계 사람들이 액세스 할 수 있는 웹 애플리케이션을 구축하는 경우에 Blazor는 가장 적합한 프레임워크가 될 수 없습니다.

 

Blazor 점수

 

위 다이어 그램에서 Lighthouse 점수는 Blazor앱에 몇 가지 심각한 성능 문제가 있음을 보여줍니다. 초기 페이지 로드 시 필요한 종속성을 다운로드해야 하므로 초기 페이지 로드 시간이 느립니다.

 

React 점수

 

이 부분에 대해서 React가 빛을 발합니다. UI 라이브러리로서 React 핵심 패키지는 매우 간결합니다. 구성 요소 기반 패러다임을 사용해 놀랍도록 빠르고 현대적인 클라이언트 측 애플리케이션을 구축하기 위해 완전히 최적화되어 있습니다. 

 

 

 

8. 생태계.

 

이 글을 쓰는 시점에서 Microsoft는 Blazor WebAssembly 및 Blazor Server를 포함해 4개의 새로운 Blazor 에디션을 발표했습니다.

 

반면에 React 생태계는 구현하려는 거의 모든 것에 대한 패키지를 npm에서 찾을 수 있을 정도로 매우 큽니다. React는 Facebook의 전폭적인 지원을 받으며 클라이언트 측 애플리케이션이 구축되는 방식이 많이 바뀌었기 때문에 커뮤니티에서 많은 지원을 받았습니다. 또한 React는 여전히 JavaScript이기 때문에 웹 개발자에게 익숙합니다.

 

React의 판매 가치는 "한번 배워서 어디서나 사용"입니다. 기본적으로  React, React DOM, React Native를 사용하면 고도화되고 풍부한 대화형 프런트엔드 웹, 안드로이드, iOS 애플리케이션을 구축할 수 있습니다. 이는 대부분의 회사가 React와 React Native에 능숙한 개발자를 고용해 비교적 저렴한 비용으로 견고한 제품과 플랫폼을 쉽게 구축할 수 있도록 합니다. 

 

 

 

9. GitHub 평가.

 

이 글을 작성하는 시점에 React는 GitHub에서 188,000개 이상의 별을 받았습니다. 틀림없이 일반적으로 가장 사랑받는 JavaScript 라이브러리 중 하나입니다. 한편 Blazor는 GitHub에서 약 28,000개의 별을 받았습니다.

 

Blazor가 2018년도에 처음 출시되었고 이 글을 시점에서 개발자 커뮤니티 내에서 비교적 새롭다는 사실을 감안하면 타당한 이유가 된다고 생각합니다.

 

 

 

10. PWA(Progressive Web App) 지원.

 

Blazor PWA는 개발자가 고급 Progressive Web App을 빌드할 수 있도록 지원합니다. React 애플리케이션에서 PWA 지원을 추가하는 것은 아주 쉽습니다. 다음 명령을 실행하면 서비스 워커 파일이 추가된 React 앱이 초기화됩니다.

 

npx create-react-app my-app --template cra-template-pwa

 

 

 

11. Blazor Native와 Blazor Hybrid vs. React Native

 

이 글을 작성하는 당시 실험적인 Blazor Native를 사용하면 기본적으로 개발자가 Mobile Blazor 바인딩을 사용해 Blazor로 모바일 앱을 빌드할 수 있습니다. C#과 dotNet을 사용해 Blazor로 안드로이드와 iOS 앱을 개발하는 것은 실제로 가능합니다.

 

Blazor Hybrid 앱은 단일 앱에 기본 UI와 웹 UI를 결합한 것입니다. Blazor를 사용하면 앱의 기본 UI를 작성하고 앱에 웹 UI를 만들 수 있으므로 Blazor를 사용해 웹과 모바일 앱 모두 빌드할 수 있습니다. 기본적으로 웹과 모바일 앱에서 코드 스니펫을 공유합니다. 이는 확실히 C# dotNet 개발을 하기 좋은 타이밍입니다.

 

React는 React Native를 사용하여 네이티브 모바일 앱을 빌드하므로 React 개발자가 React로 모바일 앱을 빌드할 수 있습니다. React Native를 사용하면 기본 UI 컨트롤을 사용하며 기본 플랫폼에 대한 전체 액세스 권한을 가질 수 있습니다.

 

React Native는 프로덕션 단계에서 사용되지만 Blazor Native는 아직 개발 단계에 있습니다. React Native와 비교할 때 Blazor Native는 커뮤니티 지원이 부족합니다.

 

 

 

12. 패키지 관리자.

 

다른 JavaScript 프레임워크나 라이브러리와 마찬가지로 React는 종속성을 관리하기 위한 패키지 관리자로 npm과 Yarn을 사용합니다. Blazor WebAssembly앱에서는 다음 방법 중 하나로 패키지를 손쉽게 설치할 수 있습니다: PackageReference, dotNET CLI, Package manager, Paket CLI.

 

PackageReference를 사용해 패키지를 설치하려면 Balzorize.csproj 파일로 이동해 ItemGroup 태그 내에 다음과 같이 패키지를 추가합니다:

 

<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />

 

dotNET CLI를 사용해 패키지를 설치하려면 터미널에서 앱의 루트 디렉터리로 이동하고 다음 명령을 실행합니다: 

 

dotnet add package Microsoft.AspNetCore.Blazor.HttpClient --version 3.2.0-preview3.20168.3

 

dotNET CLI 및 Paket CLI에 대해 알아보려면 가이드를 확인하시기 바랍니다.

 

 

 

13. 구성요소 간의 통신.

 

기본적으로 React는 구성요소의 상태를 처리하기 위해 두 가지 주요한 접근 방식을 제공합니다. 구성요소는 자체 상태나 데이터를 처리하거나 props를 통해 데이터를 받을 수 있습니다. HTTP 섹션에서 구성요소가 자체 상태를 처리하는 방법을 확인할 수 있습니다. 

 

다음은 구성요소가 React 앱의 상위 구성요소에서 props를 통해 데이터를 받는 방법의 예시입니다: 

 

// Parent Component
export default function Blog() {
    const blogPosts = [
        {
            id: 1,
            title: 'This is the title',
            content: 'This is some random content',
        },
		//...
        {
            id: 4,
            title: 'This is the title',
            content: 'This is some random content',
        },
    ]
    return (
        <>
            <BlogCard blogPosts={blogPosts}/>
        </>
    )
}

 

블로그 게시물을 포함하는 각 객체의 배열인 blogPosts를 하위 구성요소인 BlogCard 구성요소에 다음과 같이 전달합니다: 

 

// Child Component
export default function BlogCard( { blogPosts } ) {
    return (
        <>
            {blogPosts.map(blogPost => (
                <div className="blog-post" key={blogPost.id}>
                    <h1>{blogPost.title}</h1>
                    <p>{blogPost.content}</p>
                </div>
                ))}
        </>
    )
}

 

이제 하위 구성요소인 BlogCard를 렌더링 할 때 props를 통해 블로그 게시물 목록을 받아올 수 있습니다. React의 props에 대해 더 자세히 알아보려면 공식 문서를 확인해 주시기 바랍니다. 

 

Blazor에서 자식 구성요소는 다음과 같이 매개변수를 통해 부모 구성요소에서 데이터를 받아옵니다:

 

// Child Component
<h2>@Title</h2>
<p>@Content</p>
@code {    
   // Demonstrates how a parent component can supply parameters
    [Parameter]
    public string Title { get; set; }
    [Parameter]
    public string Content { get; set; }
}

 

BlogCard 구성요소를 렌더링 할 때 제목과 콘텐츠를 전달할 수 있으며 다음과 같이 렌더링 됩니다:

 

<BlogCard Title="What is Blazor?" Content="Blazor is a web UI framework using C#/Razor and HTML..." />

 

 

 

14. 라우팅.

 

React 애플리케이션에서 라우터는 미리 구성되거나 설치되지 않습니다. React Router 패키지는 대부분 클라이언트 측 탐색을 구현하는 데 사용됩니다. 

 

문서에 따르면 React Router는 React Native  프로젝트를 포함하여 React가 렌더링 되는 모든 곳에서 작동합니다. API는 정말 간단하지만 URL 매개변수, 구성요소 리디렉션, 지연 로딩, 페이지 전환, 중첩 라우팅 등과 같은 많은 강력한 기능을 처리합니다.

 

React 앱에서 라우팅을 설정하려면 일반적으로 react-router-dom 패키지를 설치하고 React Router DOM 패키지의 브라우저 라우터 모듈을 사용해 index.js에 전체 앱을 래핑 합니다. 그런 다음 App.js 구성요소에서 React Router DOM의 경로 모듈을 사용해 페이지를 렌더링 합니다.

 

다음 이미지는 React에서 라우팅이 동작하는 방식을 보여줍니다:

 

Routing

 

Routing Path

 

Blazor WebAssembly 클라이언트 측 애플리케이션에서 라우팅 시스템은 ASP.NET의 기존 라우팅 엔진에 의존합니다. @page 지시문을 사용하고 파일 맨 위에 연결하려는 경로를 사용하여 Blazor 구성요소에 대한 경로를 매핑할 수 있습니다.

 

다른 페이지로 이동하려면 react-router-dom에서 NavLink 구성요소가 작동하는 방식과 유사한 NavLink 구성요소를 사용해야 합니다. 

 

NavLink

 

다음 코드를 사용해 파일 맨 위에 NavigationManager를 넣어 Blazor 앱의 페이지를 탐색합니다.

 

@inject NavigationManager NavManager

 

그런 다음 아래와 같이 주입한 NavManager의 NavigateTo 메서드를 호출합니다:

 

@inject NavigationManager NavManager
<p>Learn more about us</p>
<button @onclick="navigateHome">Go back home</button>
@code {
    private void navigateHome()
    {
        NavManager.NavigateTo("");
    }
}

 

 

 

15. HTTP

 

Blazor 애플리케이션에서 HTTP 요청을 다루는 법에 대해 알아보기 위해서 페이지 디렉터리에 FetchPost.razor 파일을 만듭니다:

 

@page "/http"
@inject HttpClient Http
<h1>Blog post</h1>
@if (posts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Body</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var post in posts)
            {
                <tr>
                    <td>@post.title</td>
                    <td>@post.body</td>
                </tr>
            }
        </tbody>
    </table>
}
@code {
    private BlogPost[] posts;
    protected override async Task OnInitializedAsync()
    {
        posts = await Http.GetJsonAsync<BlogPost[]>("https://jsonplaceholder.typicode.com/posts");
        Console.WriteLine(posts);
    }
    public class BlogPost
    {
        public int id { get; set; }
        public String title { get; set; }
        public String body { get; set; }
    }
}

 

이 코드에서 세 가지 일이 일어나고 있습니다. 먼저 HTTP 클라이언트를 사용해 HTTP 호출을 수행하는데 도움을 주는 서비스인 HttpClient를 주입합니다. Blazor 3.1.0 버전부터 Blazorize.csproj 파일에 Blazor HTTP 클라이언트를 추가해야 합니다.

 

그리고 GetJsonAsync() 메서드를 사용하여 HttpClient를 호출해 정의된 엔드포인트에서 JSON을 가져옵니다. 마지막으로 가져온 JSON을 기반으로 BlogPost 결과를 만듭니다.

 

HttpClient를 주입하려면 상단에 @inject HttpClient Http를 추가해야 합니다. 데이터를 능동적으로 가져오기 위해 HttpClient를 호출합니다. 페이지가 초기화될 때 실행이 보장되는 수명 주기 메서드인 OnInitializedAsync()를 정의하고 이를 수행합니다.

 

React와 같은 라이브러리를 사용하는 이점 중 하나는 도구의 유연성입니다. React는 Blazor와 같은 HTTP 클라이언트를 제공하지 않습니다. React 개발자는 Fetch API, Axios, XHR을 사용해 HTTP 요청을 할 수 있습니다.

 

여기서 HTTP 요청의 경우 JavaScript Fetch API를 사용합니다. 아래 코드는 내장 JavaScript Fetch API를 사용해 React 앱에서 간단한 HTTP 요청을 만드는 방법을 보여줍니다:

 

// post.js
import React, { useState, useEffect } from 'react'
export default function Post() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
        fetch("https://jsonplaceholder.typicode.com/posts")
            .then(response => response.json())
            .then(post=> {
                setPosts(post)
                setLoading(false)
            })
    }, [])
   return (
        <div>
            {
            loading ?
                (
                    <p>Loading...</p>
                ) : (
                    posts.map((post, i) => (
                        <div>
                            <p>{post.title}</p>
                            <p>{post.body}</p>
                        </div>
                    ))
                )
            }
        </div>
    )
}

 

useEffect Hook에서 엔드포인트를 호출하고 있는 것에 주목하시기 바랍니다. React는 렌더링 후 구성요소가 API에서 데이터를 가져와야 한다고 알려줍니다. 그런 다음 요청이 성공하면 setPosts를 호출하고 API에서 post 객체를 전달해야 합니다. React에서 userState Hooks가 작동하는 방식에 대한 자세한 내용은 공식 문서를 확인해 주시기 바랍니다.

 

 

 

16. 결론.

 

SPA를 구축하기 위한 최상의 프런트엔드 프레임워크를 선택하는 것은 팀 내 선호도, 에코시스템, 성능, 확장성을 포함한 많은 요인에 따라 달라집니다. 이 글에서는 React와 Blazor를 비교해 이 두 가지 멋진 프레임워크가 어떻게 작동되고 있는지 확인해 보았습니다.

 

React와 Blazor는 몇 가지 면에서 유사하며 동일한 작업을 수행하는 데 사용할 수 있습니다. 그렇다면 어떤 것을 선택해야 할까요? 인기도, 구축 중인 프로텍트 우형, 확장성, 유지 관리 가능성 등 다양한 요인을 고려해야 합니다.

 

이 글을 통해 다음 프로젝트를 위한 프런트엔드 프레임워크를 선택할 때 도움이 되었길 바랍니다. 즐거운 코딩 되세요.

 

 

 

 

 

반응형

 

 

 

꽤 오래전 vsftpd 관련 글을 포스팅했을 때 당연히 TLS 설정도 해둔 줄 알았으나 SFTP로 연결이 되지 않아 확인해보니 TLS 설정이 없어 이번 기회에 관련 글을 남깁니다.

 

 

 

1. 인증서 생성

 

다음 명령어를 통해 인증서를 생성합니다.

 

sudo mkdir /etc/ssl/private
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/vsftpd.pem -out /etc/ssl/private/vsftpd.pem

 

 

 

2. 설정파일 변경

 

설정 파일을 열어 맨 아래에 다음과 같은 설정을 추가합니다. 인증서 경로는 앞서 생성한 경로로 설정합니다.

 

pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
rsa_cert_file=/etc/ssl/private/vsftpd.pem
rsa_private_key_file=/etc/ssl/private/vsftpd.pem
ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=NO
ssl_ciphers=HIGH
allow_writeable_chroot=YES
listen_port=21
ftp_data_port=20
pasv_enable=YES
pasv_min_port=22
pasv_max_port=42
port_enable=YES

 

 

 

3. vsftpd 재시작 및 접속 테스트

 

vsftpd를 재시작하고 클라이언트로 접속을 시도합니다. 접속 설정은 다음과 같이 한 뒤 접속해 봅니다.

 

 

접속하면 다음과 같이 인증서를 확인할 수 있습니다.

 

 

이후 정상적으로 SFTP를 사용할 수 있습니다.

 

 

 

 

 

반응형

 

C# MongoDB MongoDB Driver를 구식 버전에서 업그레이드 함에 따라 변경된 문법을 정리.

 

 

MongoClient

// Old
MongoClient client = new MongoClient("CONNECTION_STRING");
// New: Same syntax.
MongoClient client = new MongoClient("CONNECTION_STRING");

 

 

MongoServer

//Old
MongoServer server = client.GetServer();
//New: MondoServer is deprecated.

 

 

MongoDatabase

// Old
MongoDatabase db = server.GetDatabase("DATABASE_NAME");
// New
IMongoDatabase db = client.GetDatabase("DATABASE_NAME");

 

 

Run Ping

// Old
db.RunCommand("ping");
// New
db.RunCommand<BsonDocument>("{'ping': 1}");

 

 

MongoCollection

// Old
MongoCollection mongoCollection = mongoDatabase.GetCollection<YourDocumentClass>("COLLECTION NAME");
// New
IMongoCollection<YourDocumentClass> mongoCollection = mongoDatabase.GetCollection<YourDocumentClass>("COLLECTION NAME");

 

 

Check the collection is exists

// Old
mongoCollection.Exists();
// New
if(mongoCollection != null)  {}

 

 

Check the index is exists

// Old
mongoCollection.IndexExistsByName(collectionIndex);
// New
listIndexes.SelectMany(idx => idx.Elements).Where(elem => elem.Name.Contains("name")).Select(name => name.Value.ToString()).Contains("INDEX_NAME");

 

 

Create a new index

// Old
IndexKeysBuilder<YourDocumentClass> keys = IndexKeys<YourDocumentClass>.Ascending(x => x.f1).Descending(x => x.f2);
IndexOptionsBuilder options = IndexOptions.SetName("INDEX_NAME");
mongoCollection.CreateIndex(keys, options);
// New
CreateIndexModel<YourDocumentClass> indexModel = new CreateIndexModel<YourDocumentClass>( 
    Builders<YourDocumentClass>.IndexKeys.Ascending(x => x.f1).Descending(x => x.f2),
    new CreateIndexOptions(){ Name: "INDEX_NAME"})
);
mongoCollection.Indexes.CreateOne(indexModel);

 

 

Find Query - FindOne

// Old
var findQuery = Query<YourDocumentClass>.EQ(x => x.f1, "VALUE");
YourDocumentClass doc = collection.FindOneAs<YourDocumentClass>(findQuery);
// New
var findQuery = Builders<YourDocumentClass>.Filter.Eq(x => x.f1, "VALUE");
List<YourDocumentClass> listYourDocumentClass = collection.Find(findQuery).Limit(1).ToList();
YourDocumentClass doc = listYourDocumentClass[0];

 

 

Update Query

// Old
var updateValues = Update<YourDocumentClass>.Set(x => x.f1, "VALUE");
WriteConcernResult result = collection.Update(findQuery, updateValues, UpdateFlags.Multi)
// New
var updateQuery = Builders<YourDocumentClass>.Update.Set(x => x.f1, "VALUE");
collection.UpdateMany(findQuery, updateQuery);

 

 

Delete Query

// Old
var query = Query<YourDocumentClass>.EQ(x => x.f1, "VALUE");
collection.Remove(query);
// New
var findQuery = Builders<YourDocumentClass>.Filter.Eq(x => x.f1, "VALUE");
collection.DeleteMany(findQuery);

 

 

Insert Query

// Old
collection.Insert(YourDocumentClass);
// New
collection.InsertOne(YourDocumentClass);

 

 

 

 

 

 

반응형

+ Recent posts