[OSSCA] Challengers 1주차 Azure Function

뚜비·2022년 7월 26일
0

2022 OSSCA

목록 보기
2/14
post-thumbnail

2022 오픈소스 컨트리뷰션 아카데미 "NHN Toast Power Platform Connector" 프로젝트에 참여하면서 Challengers 기간동안 배운 내용을 기록하였습니다.


✅ 환경설정

아래의 계정 생성과 개발 환경을 모두 setting했다는 가정에서 진행

계정 생성

  1. M365 개발자 계정
  2. 파워 플랫폼 개발자 계정
  3. NHN 클라우드 계정

개발 환경 설정

  1. .NET 6 SDK
  2. Azure CLI
  3. PowerShell
  4. Visual Studio Code

Azure functions core tools 설치

Azure Functions Core Tools lets you develop and test your functions on your local computer from the command prompt or terminal
- Microsoft Docs -

The Azure Functions Core Tools provide a local development experience for creating, developing, testing, running, and debugging Azure Functions.
- Azure Functions Core Tools


먼저 VsCode를 실행하고 OPM이라는 폴더를 열어준 후 Terminal을 연다.

npm i -g azure-fuctions-core-tools@4

Terminal에 를 입력하여 Azure function 개발을 위한 Azure functions core tools를 설치한다.

명령어를 통해 설치가 잘 안된다면 Work with Azure Functions Core Tools에 가서 Install the Azure Functions Core Tools 에서 자신에게 맞는 버전을 다운받아보자.


설치가 끝났으면 .Net과 Azure functions core tools의 버전을 확인한다. 에러 없이 버전이 잘 뜨면 설치가 잘 되었다는 뜻!

dotnet --list-sdks // .Net 버전과 위치 확인

func --version // 혹은 -version, Azure functions core tools의 버전 확인

❌ 윈도우에서 func -version 입력 시 붉은 글씨로 Unauthorize 오류가 뜨면 Powershell이나 VSCode를 관리자 권한으로 열어보면 되니 참고! (감사합니다 다른 멘티분..)



✅ Azure function 생성

> func init

Terminal에서 해당 명령어를 입력하면 옵션을 설정할 수 있게 된다. 방향키와 엔터를 이용해 조작가능하다.

worker runtime 설정
1번 dotnet 엔터

language 설정
1번 C# 엔터

> func new

정상적으로 옵션이 설정되면 Termianal에 해당 명령어를 입력한다.

template 설정
2번 HttpTrigger 엔터

이때 원하는 function name을 지정할 수 있는데 교육에서는 PingHttpTrigger로 지정해주었다.


> dotnet restore

여러 파일들이 생성된 후 지정된 종속성 및 프로젝트 관련 도구를 복원시켜준다! 일부 파일에 빨간 색 줄이 발생하면 해당 명령어를 실행시켜주자


✅ 코드 수정

Namespace 수정

디렉토리를 확인해보면 .csprojPingHttpTrigger와 같은 여러 파일들이 생성된 것을 볼 수 있다. 그리고 .csproj 파일을 열어보자. < TargetFramework >, < AzureFunctionVersion > 각 태그 안의 값이 net6.0, v4인 것을 확인할 수 있다.

<AssemblyName>OCAProject</AssemblyName>
<RootNamespace>OCAProject</RootNamespace>

namespace를 수정하기 위해 .csproj 파일에 다음과 같은 태그를 추가한다.


그리고 PingHttpTrigger.cs 파일을 열어 namespace를 수정해준다! OCAProject로 수정해줬다!

Ctrl + S로 저장 한후

dotnet build .

빌드해준다.



PingHttpTrigger 수정하기

우리는 위과 같이 코드를 수정한다.

// [FunctionName("PingHttpTrigger")]
[FunctionName(nameof(PingHttpTrigger))]

FunctionName에서 nameof(PingHttpTrigger)로 수정해준다. nameof로 해두면 문자열로 하드코딩하는 것보다 참조하기 편해진다.

// [HttpTrigger(AuthorizationLevel.Function,"get","post",Route = null)]
[HttpTrigger(AuthorizationLevel.Function,"get" , Route = "ping")]

참고링크
HttpTrigger를 사용하면 HTTP 요청으로 함수를 호출할 수 있는데, HttpTrigger를 사용하여 서버리스 API를 만들고 웹후크에 응답할 수 있다!

HttpTrigger를 수정하는데 우리는 post하는 작업은 필요없기 때문에 지워주고 Route="ping"으로 설정해주어 end point 주소를 설정해준다.


그리고 다음 코드를 주석 처리해준다.

string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;

dotnet build . 로 다시 빌드하고, func start를 입력하면 Terminal에 http://localhost:7071/api/ping url이 뜬다. 이를 복사해서 웹 브라우저에서 실행시켜보면 쿼리가 입력되지 않았을 때의 responseMessage 값을 볼 수 있다.
위의 코드는 responseMessage를 다루는 코드!

다시 ?name=OCA와 같이 query string 을 추가해서 접속해보면 query가 포함된 responseMessage 값을 볼 수 있다.


OCA가 포함된 채로 문구가 뜬다.



Response Message 클래스 생성

// public static async Task<IActionResult> Run
public static IAtionResult Run

static과 async keyword를 지워주고

public class ResponseMessage{
    [JsonProperty("response_message")] // 응답 메세지를 json 객체로 우리 맘대로 바꿔줄 수 있음, () 안의 값은 Json 응답 객체의 key를 변경해준 것!! 
    public string Message{get; set;}
}

ResponseMessage class를 PingHttpTrigger class 뒤에 만들어준다! JsonProperty를 추가하면 기존의 문자열 response를 JSON response로 바꿔줄 수 있다.

참고로 [ JsonProperty() ]의 default key값은 message이다.


Run method를 아래의 코드처럼 수정해주고 dotnet build . -> func start하면, 위의 사진에서 볼 수 있는 응답이 보일 것이다.


 public IActionResult Run( // end point 만 담당 
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "ping")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"]; // 쿼리만 뽑고 

            // string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // dynamic data = JsonConvert.DeserializeObject(requestBody);
            // name = name ?? data?.name; 필요없음

            string responseMessage = string.IsNullOrEmpty(name)
                 ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                 : $"Hello, {name}. This HTTP triggered function executed successfully.";
            
            /*추가된 부분*/
            var res = new ResponseMessage() {Message = result};

            return new OkObjectResult(res);
        }


Service 구현

주요 서비스를 분리하는 작업을 한다!
ResponseMessage 뒤에 다음과 같은 인터페이스와 클래스를 생성한다.

public interface IMyService{
    //인터페이스 
    string GetMessage(string name);
}

public class MyService : IMyService{ // 인터페이스로 의존성을 준것!! 
    public string GetMessage(string name){
        string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

        return responseMessage;
    }
}

Service 클래스를 Interface 에 의존하도록 하여 의존관계를 역전 시키고 느슨한 결합 을 만들어 준다. 이때 Service 클래스 안에는 reponseMessage 처리에 관련된 내용을 그대로 구현했다.


다음과 같이 Run method를 수정해준다!

 public IActionResult Run( // end point 만 담당 
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "ping")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"]; // 쿼리만 뽑고 

            // string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            // dynamic data = JsonConvert.DeserializeObject(requestBody);
            // name = name ?? data?.name; 필요없음

            // string responseMessage = string.IsNullOrEmpty(name)
            //     ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
            //     : $"Hello, {name}. This HTTP triggered function executed successfully.";

            //여기에 비즈니스 로직이 존재 , 우리가 고민해봐야 할 부분 
            var service = new MyService(); 
            var result = this._service.GetMessage(name);

            var res = new ResponseMessage() {Message = result};

            return new OkObjectResult(res);
        }

이제 Run method는 http request 로 받은 값을 Service로 넘겨주고 받은 값을 반환하는 역할만 하게 된다.



리팩토링

하지만 여전히 ServicePingHttpTrigger와 강한 의존성이 존재한다. MyService 클래스가 PingHttpTrigger 클래스안에서 생성이 되고 있기 때문이다.


이를 위해 PingHttpTrigger의 static을 지우고 Service 멤버 변수PingHttpTrigger의 생성자를 만들어준다.

 public class PingHttpTrigger
    {
        private readonly IMyService _service;

        public PingHttpTrigger(IMyService service){ // 의존성을 없애주려는 코드
            this._service = service ?? throw new ArgumentNullException(nameof(service)); // service에 값이 없으면 에러나고 이 func 실행시키지 말아라 
        }

이때 생성자는 Service 인터페이스를 파마리터로 받고, 멤버 변수에 할당, 이때 service가 null인 경우 NullException을 던지게 한다.


			//여기에 비즈니스 로직이 존재 , 우리가 고민해봐야 할 부분 
            // var service = new MyService(); 의존성 제거 
            var result = this._service.GetMessage(name);

            var res = new ResponseMessage() {Message = result};

            return new OkObjectResult(res);

Run 메소드를 다음과 같이 수정하게 되면서 PingHttpTriggerMyServiceInterface를 통하여 연결되기 때문에 의존성을 낮추었다.



✅ 패키지 추가 설치

Terminal에서 추가 패키지를 설치하면

dotnet add package Microsoft.Azure.Functions.Extensions
dotnet add package Microsoft.Extensions.Http

.csproj 파일에서 < ItemGroup > 태그에 위과 같은 패키지가 설치되는 것을 볼 수 있다.



그리고 Startup.cs 클래스 파일을 루트 디렉토리 안에 생성한다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(OCAProject.Startup))]
namespace OCAProject{

    public class Startup: FunctionsStartup
    {

        public override void Configure(IFunctionsHostBuilder builder){
            builder.Services.AddScoped<IMyService, MyService>();
            
        }
    }
}

builder.Services.AddScoped<IMyService, MyService>(); 를 통해 의존성을 설정해준다. 즉 Startup.cs 클래스는 의존성 주입을 위해 생성하였다.



의존성(종속성) 주입은 왜 필요한걸까?

  • OOP에서는 하나의 클래스가 하나의 기능만을 담당하도록 하는 것을 권장한다.
    -> 각각의 cs파일들이 하나의 클래스만을 담을 수 있도록 디렉토리 및 파일을 생성하고 수정해야한다.
  • 또한 OOP는 각각의 클래스들이 강하게 결합되어 의존하지 않는 것을 권장한다.
    -> 이를 위해 종속성 주입을 해줘야 한다.

IoC Container
IoC 컨테이너는 Inversion of Control(제어 역전) 컨테이너를 의미한다. 우리가 만든 코드의 종속성 주입을 IoC 컨테이너가 해준다고 생각하면 된다.
Inversion of Control in C#

오류선이 안 사라진다!!
코드에서 계속 오류선이 사라지지 않는다면
1. 다음의 두 VS Code Extension 을 설치
- C#
- C# Extensions
2. 오류선에 마우스 커서로 클릭하고
- 윈도우라면 ctrl + .
- 맥이라면 cmd + .



✅ OpenApi 등록

Terminal에 다음 명령을 통해 추가!

dotnet add package Microsoft.Azure.WebJobs.Extensions.OpenApi

다음과 PingHttpTrigger 클래스에 OpenApi에 등록하기 위한 attribute를 추가해준다!

[OpenApiOperation(operationId: "Ping", tags: new[] {"greeting"})] // OpenApi 등록과정 
[OpenApiSecurity(schemeName: "function_key", schemeType: SecuritySchemeType.ApiKey, Name = "x-functions-key", In = OpenApiSecurityLocationType.Header)]
 // OpenApi credential을 설정
 // 스키마네임은 OpenAPi에서 function_key라는 이름으로 정의하겠다. 
 // function key는 앤드 포인트마다 다르다 앤드 포인트 하나당 메서드 하나라고 볼 수 있음!! 
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required=true, Description ="Name of ther person to greet.")]
 // OpenApi parameter를 설정
 // 인풋값이 들어왔다~~~~ 쿼리 스트링의 name에 데이터를 뽑아오겠다. 
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "application/json", bodyType: typeof(ResponseMessage), Description="Greeting Message")]
 // OpenApi response를 설정, 즉 아웃풋


다음의 namespace가 소스 파일에 있는지 확인한다.

using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Attributes;
using Microsoft.Azure.WebJobs.Extensions.OpenApi.Core.Enums;
using Microsoft.OpenApi.Models;
using System.Net;

이제 다시 dotnet build . -> func start하면

다음과 같은 앤드 포인트들이 보인다.

브라우저에 http://localhost:7071/api/swagger/ui 에 접속하면 Swagger 창을 확인할 수 있다!

짜잔!



🙏 참고자료

  • 나의 공부 기록

동료 멘티 분들의 기록...

함께하는 멘티님들께 무한한 감사를... 정리를 너무 잘해주셨다..



이건 호준멘티님이 만든 UML Diagram인데.. 정말 잘 정리된 Diagram이다..
내가 지금까지 배우고 따라한게 Diagram으로 보면 되게 복잡해보이네?! 하면서도 그리 어렵지만은 않구나를 느끼게 되었다.



진짜 땀 뻘뻘 흘리면서 배웠는데 진짜 재밌었다.

profile
SW Engineer 꿈나무 / 자의식이 있는 컴퓨터

0개의 댓글