ejyoo's 개발 노트

Minimal API 만들기 본문

BackEnd/.Net Core

Minimal API 만들기

ejyoovV 2023. 6. 2. 21:33

.NetCore 의 Minimal API 만들기

MS 사이트에서 Tutorial로 있는 예제를 참고하여
간단한 프로젝트를 실습한다.
 

  • 실습 내용 (환경 : .NET 7.0 / .NET SDK)
    • minimap API 생성과 API 호출 (API 호출 도구)
    • EF DBSet 생성
    • 모델 생성과 마이그레이션 코드 생성
    • 도커로 디비 올리고 API 호출하여 디비에 데이터 등록하기

https://learn.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-6.0&tabs=visual-studio-code 

 

Tutorial: Create a minimal API with ASP.NET Core

Learn how to build a minimal API with ASP.NET Core.

learn.microsoft.com

 


닷넷 설치 확인 및 프로젝트 생성

먼저 dotnet, sdk 설치 확인을 한 후 설치가 되어있지 않다면 설치해야 한다.

  • 설치 확인 방법
dotnet --version
dotnet sdk check

프로젝트를 생성할 실습용 폴더를 만든다.

 mkdir minimalService-day2-ej
 cd minimalService-day2-ej

 
만든 실습용 폴더에서 .NET Core CLI (Command Line Interface) 명령어를 입력하여
프로젝트를 생성한다.

dotnet new web -o .


'dotnet new web -o .'  CLI
더보기

dotnet new web
기본적인 웹 애플리케이션을 생성하는 명령어이다.

Startup.cs, Program.cs 파일 등 기본 구조를 가진 간단한 웹 애플리케이션 프로젝트를 생성한다.

 

'-o .'  (--output)

'-o .' 는 --output 으로 풀어쓸 수 있고, 생성된 프로젝트가 위치할 디렉토리를 지정한다.

.은 현재 디렉토리를 의미한다.

 

따라서 이 명령어는 현재 디렉토리에 새로운 .NET Core Web 프로젝트를 생성한다.

Program.cs
더보기
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

.NET 6 이상에서 사용되는 최소한의 웹 API 애플리케이션을 설정하는 코드이다.

 

var builder = WebApplication.CreateBuilder(args);

웹 애플리케이션의 빌더를 생성한다. 
이 빌더는 애플리케이션의 설정, 서비스, 미들웨어 파이프라인 등을 구성하는데 사용된다.

 

var app = builder.Build();

WebApplication 인스턴스를 생성하는 데 사용되는 빌드 메서드를 호출한다. 
이 app 객체는 HTTP 요청 파이프라인을 구성하고 웹 서버를 실행하는데 사용된다.

 

app.MapGet("/", () => "Hello World!");

요청 경로와 그에 대응하는 처리함수를 매핑한다. 
HTTP GET 요청이 루트 경로("/")로 들어올 때 "Hello World!" 라는 문자열을 반환하도록 설정한다.

 

app.Run();

웹 애플리케이션을 시작하고 HTTP 요청을 수신하도록 웹 서버를 실행한다. 
이 메서드는 비동기적으로 실행되고, 애플리케이션이 실행 중인 동안 블록된다.


프로젝트 빌드 / 런하기

프로젝트 빌드

콘솔창에 아래의 코드를 입력한다.

dotnet build;

프로젝트 빌드를 하게되면 Debug 폴더 안에 dll이 생성된다.

프로젝트 런

빌드한 후 프로젝트 런한다.

dotnet run;

빌드된 파일을 기준으로 웹서버를 실행한다.

 

기본  프로젝트를 실행하게 되면 

"Hello World!" 라는 메시지를 반환하는 간단한 웹 서버를 볼 수 있다.

 

콘솔창 또는 launchSetting.json 의 profiles - applicationUrl 주소를 확인하여

크롬에 주소를 입력하고 접속한다.

결과화면


추가 API 생성하기

추가 API CRUD 를 생성하고 API Test Tool (Swagger / Postman) 을 사용하여 API 실행 테스트를 해볼 것이다.

디비관련 NuGet Package 설치

CRUD API 를 구성하기 전, EF Core NuGet Package 를 설치해야 한다.

 dotnet add package Microsoft.EntityFrameworkCore.InMemory
 dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
NuGet Package 명령어
더보기

dotnet add package

.NET Core 프로젝트에 NuGet 패키지를 추가하는 데 사용된다.

 

dotnet add package Microsoft.EntityFrameworkCore.InMemory

이 명령어는 Microsoft.EntityFrameworkCore.InMemory 패키지를 프로젝트에 추가한다.

Entity Framework Core의 InMemory 데이터베이스 공급자를 사용하려면 이 패키지가 필요하다.

주로 개발 단계나 테스트 시나리오에서 사용되고,
실제 데이터베이스를 대신하여 메모리 내에 데이터를 저장하고 검색한다.

 

dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore 패키지를 프로젝트에 추가한다.

ASP.NET Core 응용 프로그램에서 Entity Framework Core와 함께 진단(diagnostics)를 사용하려면 필요하다.

Entity Framework Core에서 발생하는 예외를 잡아내고, 예외에 대한 유용한 디버깅 정보를 제공하며,
데이터베이스 오류 페이지와 관련된 기능도 제공한다.

 

두 개의 명령어를 실행하면 

프로젝트 파일(.csproj 또는 .fsproj 등)에 해당 패키지에 대한 참조가 추가되고,

패키지 복원 과정에서 NuGet 프로젝트에 다운로드된다.


모델 설계

모델관련 파일이 들어갈 폴더를 생성한다.

mkdir models

models 에서 Todo.cs 를 생성하고 모델 클래스를 정의한다.

public class Todo
{
  // 투두 아이디
  public int Id { get; set; }

  // 투두 이름
  public string? Name { get; set; }

  // 투두 삭제 여부
  public string? IsDeleted { get; set; }

  // 수행여부
  public bool IsComplete { get; set; }

  // 생성자
  public string? Creator { get; set; }

  // 생성 시간
  public DateTime CreatedTime { get; set; }

  // 수정자
  public string? Editor { get; set; }

  // 수정시간
  public DateTime EditedTime { get; set; }
}
string? 을 쓰는 이유
더보기

C# 8.0 버전부터 nullable reference types 이라는 기능이 추가되었다.

이 기능은 참조 타입이 null 값을 가질 수 있음을 명시적으로 나타내는 것이다.

 

기존에는 참조 타입이 기본적으로 null을 가질 수 있었지만,

이제는 개발자가 명시적으로 표시해야 하는 것이다.

 

실제 코드에서,

public string test { get; set; }

 를 입력하면, 컴파일러에서 경고를 표시한다.

 

이는 string? 형식으로 변경하라는 경고이다. 이것은 nullable reference types 의 도입과 관련이 있다.

이는 코드를 통해 '이 문자열이 null 이 될 수 있음' 을 명시적으로 나타내는 것이다.

 

이 경고는 null 값이 될 수 있는 참조 타입에 대해 예상치 못한 null 참조 예외를 방지하기 위해,

null처리를 제대로 해야 함을 알려주는 것이다.

이 경고를 무시하고 string을 사용할 수 있지만,

null 참조 오류가 발생할 가능성이 있다는 것을 인식하고 적절한 null 처리를 해야 한다.


DBContext 정의

데이터 모델에 대한 EF Core를 사용하기 위해 (데이터 베이스 연산) 기본 클래스인 DB Context를 정의한다.

using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{ 
  public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) 
  {

  }

  public DbSet<Todo> Todos { get; set; }
}

 

[Program.cs] InMemoryDatabase builder에 추가

DI (Dependency Injection) 컨테이너에 AppDbContext라는 DB 인스턴스를 등록한다.

builder 인스턴스는 인메모리 데이터 베이스를 사용하도록 구성한다.

AppDbContext가 사용할 데이터베이스 연결을 구성하는 부분으로  

데이터베이스를 Todos를 사용한다.

 

메모리 내 데이터베이스는 주로 개발이나 테스트 시 사용되고,

실제 운영환경에서는 실제 데이터 베이스 연결로 변경한다.

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("Todos"));

오류페이지 관련 필터 추가

개발자 오류 페이지 필터이다.

이 필터를 서비스에 추가하여

데이터베이스 관련 오류가 발생했을 때 자세한 오류 정보를 제공한다.

Entity Framework Core에서 발생하는 예외가 더 세부적인 정보와 함께 표시된다.

 

이것은 데이터베이스 연결 문제나 쿼리 문제 등을 디버깅하는데 도움을 준다.

 

예를 들어서 데이터베이스 마이그레이션 관련 문제가 발생했을 때,

DatabaseDeveloperPageExceptionFilter를 사용하면 오류 페이지에서 이에 대한 정보를 볼 수 있다.

 

이 오류 페이지는 대개 어떤 마이그레이션 스크립트가 실행되지 않았는지

어떤 문제 때문에 마이그레이션에 실패했는지에 대한 정보를 포함한다.

 

데이터베이스 구조나 쿼리 등의 정보가 외부에 노출될 수 있기에 이 메서드는 주로 개발환경에서만 사용해야 한다.

보안상의 이유로 운영환경에서는 사용하지 않는 것이 일반적이다.

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

 


API 코드 작성

종류 : MapGet, MapPost, MapPut, MapDelete

app 은 HTTP 요청 파이프라인을 구성하고 웹 서버를 실행되는데 사용하는 객체이다.

app을 사용하여 HTTP 각 요청을 처리하는 .NET Core의 미들웨어 라우팅 코드를 작성할 수 있다.

각 엔드포인트는 주어진 URL 패턴에 대한 요청을 처리하고 요청을 처리하는 방법은 해당 람다 함수에 정의된다.

 

다음의 코드는 HTTP GET, POST, PUT, DELETE 메소드를 처리하는 엔드포인트를 정의한다.

[GET] 모든 Todo 항목을 가져오는 API 생성

app.MapGet("/todoitems", async (AppDbContext db) =>
  await db.Todos.ToListAsync()
);

[GET] 완료된 모든 Todo 항목을 가져오는 API 생성

app.MapGet("/todoitems/complete", async (AppDbContext db) =>
  await db.Todos.Where(t => t.IsComplete).ToListAsync()
);

[GET] ID 에 해당하는 Todo 항목을 가져오는 API 생성

app.MapGet("/todoitems/{id}", async (int id, AppDbContext db) =>
  await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()
);

[POST] Todo 모델을 사용하여 새로운 Todo를 등록하는 API 생성

app.MapPost("/todoitems", async (Todo todo, AppDbContext db) =>
{
  db.Todos.Add(todo);
  await db.SaveChangesAsync();

  return Results.Created($"/todoitems/{todo.Id}", todo);
});
코드 내용
더보기

/todoitems 경로로 들어오는 HTTP POST 요청을 처리하는 엔드포인트를 설정한다.

람다 함수를 사용하여 새로운 Todo 항목을 생성한다.

아래의 라우터 핸들러는 두 개의 매개변수를 받는다. (Todo 타입의 객체 / AppDbContext 타입의 객체)

Todo 객체는 HTTP 요청 본문에서 자동으로 바인딩되고,

AppDbContext 객체는 DI (Dependency Injection) 를 통해 주입된다.

 

* db.Todos.Add(todo);

- Todo 객체를 Todos DbSet에 추가한다. 이 단계에서는 아직 데이터베이스에 변경 사항이 반영되지 않는다.

 

* db.SaveChanges();

- DbContext에 대한 모든 변경 사항을 데이터베이스에 저장하고 이에 대한 결과를 반환한다.

- Todos DbSet에 추가된 Todo 객체가 데이터베이스에 저장된다.

 

* return Result.Created($"/todoitems/{todo.Id}", todo);

- 새로 생성된 Todo 객체에 대한 정보와 함께 HTTP 201 Created 상태 코드를 응답한다.

- 응답 본문에는 Todo 객체가 포함되고

- Location 헤더에는 새로 생성된 리소스의 URI 가 포함된다.

 

[Post] 주어진 ID 에 해당하는 Todo 항목을 업데이트하는 API 생성

name 과 iscompleted 상태를 업데이트하는 todo api 생성

app.MapPost("/todoitems/{id}", async (int id, Todo inputTodo, AppDbContext db) =>
{
  var todo = await db.Todos.FindAsync(id);

  if (todo is null) return Results.NotFound();

  todo.Name = inputTodo.Name;
  todo.IsComplete = inputTodo.IsComplete;

  await db.SaveChangesAsync();

  return Results.Ok();
});

[Delete] 주어진 ID에 해당하는 Todo 항목을 삭제하는 API

클라이언트가 id 값을 url에 포함하여 delete 요청을 보낼 수 있고,

해당 ID 를 가진 항목이 데이터베이스에서 삭제되면 삭제된 항목이 반환되는 API 이다.

만약 해당 ID를 가진 항목이 없다면, 클라이언트는 'Not Found' 응답을 받는다.

app.MapDelete("/todoitems/{id}", async (int id, AppDbContext db) =>
{
  if (await db.Todos.FindAsync(id) is Todo todo)
  {
    db.Todos.Remove(todo);
    await db.SaveChangesAsync();
    return Results.Ok(todo);
  }

  return Results.NotFound();
});
코드 내용
더보기

ASP.NET Core - HTTP DELETE 요청을 처리하는 라우팅 미들웨어를 설정한다.

이 코드는 주어진 'id'를 가진 'Todo' 항목을 데이터베이스에서 삭제하는 역할을 한다.

 

* app.MapDelete("/todoitems/{id}", async (int id, AppDbContext db) => {...})

- HTTP DELETE 요청에 대한 라우트 매핑을 설정한다.

- 클라이언트가 'todoitems/{id}' 경로를 통해 DELETE 요청을 보낼 때, 해당 요청을 처리하기 위한 미들웨어를 설정한다.

- 여기서 '{id}' 는 URL 의 일부로 전달되는 Todo 항목의 ID를 나타낸다.

- 요청이 이 미들웨어에 도달하면, '{id}' 는 int 변수 'id'로 바인딩되고, 'AppDbContext'는 데이터베이스 컨텍스트인 'db'로 주입된다.

 

* if (await db.Todos.FindAsync(id) is Todo todo) {...}

- 데이터베이스에서 해당 ID를 가진 'Todo' 항목을 비동기적으로 찾는다.

- 찾은 항목은 'todo'라는 변수에 할당된다.

- 만약 해당 ID 를 가진 항목을 찾지 못했다면, 이 if 문은 실행되지 않는다.

 

* db.Todos.Remove(todo)

- 찾아낸 'todo' 항목을 'Todos' DbSet 에서 삭제한다.

 

* await db.SaveChangesAsync()

- 변경 사항을 데이터베이스에 저장하기 위해 비동기적으로 실행한다.

 

* return Results.Ok(todo)

- 삭제된 'todo' 항목을 클라이언트에 반환하고 HTTP 상태코드 200 (OK) 와 함께 반환된다.

 

* return Results.NotFound()

- 해당 ID를 가진 'Todo' 항목을 찾지 못했다면, 이 줄이 실행되어 HTTP 상태 코드 404 (Not Found) 를 반환한다.


웹 애플리케이션 시작 코드

app.Run();

전체코드 (Program.cs)

더보기
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("Todos"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();


var app = builder.Build();

// [API] root page
app.MapGet("/", () => "root 입니다.");

// [API] 모든 todo list 획득
app.MapGet("/todoitems", async (AppDbContext db) =>
  await db.Todos.ToListAsync()
);

// [API] 완료된 todo list 획득
app.MapGet("/todoitems/complete", async (AppDbContext db) =>
  await db.Todos.Where(t => t.IsComplete).ToListAsync()
);

// [API] 특정 id 에 해당하는 todo 획득
app.MapGet("/todoitems/{id}", async (int id, AppDbContext db) =>
  await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()
);

// [API] todoItem 등록
app.MapPost("/todoitems", async (Todo todo, AppDbContext db) =>
{
  db.Todos.Add(todo);
  await db.SaveChangesAsync();

  return Results.Created($"/todoitems/{todo.Id}", todo);
});

// [API] 특정 id 에 해당하는 todo 업데이트
app.MapPost("/todoitems/{id}", async (int id, Todo inputTodo, AppDbContext db) =>
{
  var todo = await db.Todos.FindAsync(id);

  if (todo is null) return Results.NotFound();

  todo.Name = inputTodo.Name;
  todo.IsComplete = inputTodo.IsComplete;

  await db.SaveChangesAsync();

  return Results.Ok();
});

// [API] 특정 id 에 해당하는 todo 삭제
app.MapDelete("/todoitems/{id}", async (int id, AppDbContext db) =>
{
  if (await db.Todos.FindAsync(id) is Todo todo)
  {
    db.Todos.Remove(todo);
    await db.SaveChangesAsync();
    return Results.Ok(todo);
  }

  return Results.NotFound();
});

app.Run();

작성된 API 반영 (빌드 / 런)

dotnet build;
dotnet run;

API 코드테스트 - Postman

설치

포스트맨 설치 사이트
https://www.postman.com/downloads/

 

Download Postman | Get Started for Free

Try Postman for free! Join 25 million developers who rely on Postman, the collaboration platform for API development. Create better APIs—faster.

www.postman.com

API 메소드 선택 하고 주소를 입력한 뒤 Send 하여 요청이 올바른지 확인한다.

만약 POST 메소드인 경우, 서비스 파라미터 입력은 Body - raw 에서 JSON 형식으로 입력하여 Send 한다.

[GET] 엔트포인트 테스트

API 설명 요청 본문 응답 본문
GET /todoitems 모든 할일 목록 가져오기 없음 할 일 항목 배열
GET /todoitems/complete 완료된 모든 할일 목록 가져오기 없음 할 일 항목 배열
GET /todoitems/{id} Id 로 특정 항목 가져오기 없음 할 일 항목


API 코드테스트 - Swagger

...

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

...

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

도커를 활용해서 PostgresDB 동작시키기

Docker는 애플리케이션을 효과적으로 구축, 배포, 실행하기 위한 오픈 소스 플랫폼으로,
컨테이너라는 기술을 사용하여 환경에 구애받지 않고 동일한 방식으로 애플리케이션을 실행할 수 있게 해준다. 
 

Docker 설치 확인

docker

 
Postgres 이미지 다운

Docker Hub에서 Postgres 데이터베이스 서버의 Docker 이미지를 내려받는다.

docker pull postgres
docker pull 명령
더보기

docker pull` 명령은 Docker Hub라는 웹 기반 저장소에서 특정 이미지를 내려받는 것을 의미한다.
https://hub.docker.com/postgres

 

Docker

 

hub.docker.com

docker pull postgres` 명령을 사용하면, Docker Hub에서 Postgres 데이터베이스 서버의 Docker 이미지를 내려받는다
이 Postgres 이미지는 컨테이너를 실행할 때 Postgres 데이터베이스 서버를 자동으로 설정하고 실행해 준다.
이렇게 함으로써, 데이터베이스 설치 및 설정에 들어가는 시간과 노력을 크게 줄일 수 있다.

postgres 이미지 설치 확인

docker images

 

docker-compose.yml 작성

postgres 기본적으로 5432 포트 사용

version: "3.1"

services:
  db:
    image: postgres
    restart: always
    ports:
      - 5432:5432
    environment:
      POSTGRES_PASSWORD: nse#01

 

docker - postgres 백그라운드 실행

postgres를 백그라운드에서 실행한다.

docker-compose up -d

코드

 

더보기

docker-compose up -d 는

docker-compose.yml 파일에서 정의한 여러 컨테이너로 구성된 애플리케이션을 시작하고 백그라운드에서 실행한다.

 

만약 이 명령을 처음 실행하거나 관련 이미지가 업데이트 된 경우,

Docker 는 해당 이미지를 먼저 빌드하거나 pull 한다.

 

이미지가 이미 존재하고 최신상태이면, Docker는 해당 이미지를 기반으로 새로운 컨테이너를 시작한다.

* -d (datached mode)

- 컨테이너를 백그라운드에서 실행하고 터미널을 반환한다.

- 이 플래그가 만약 없는 경우,

  Docker Compose 는 기본적으로 모든 컨테이너의 로그를 표준 출력(stdout) 에 연결하여

  로그메시지가 계속 터미널에 표시된다.

  이 플래그를 사용하면 이러한 로그 메시지를 백그라운드에서 보지 않고 터미널을 계속 사용할 수 있다.


EFCore에 PostgreDB 연결하고 데이터를 인메모리가 아닌 DB에 저장하기

 
PostgreSQL 패키지 추가

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
코드
더보기

Npgsql.EntityFrameworkCore.PostgreSQL는 
Entity Framework Core(EF Core)의 PostgreSQL 데이터베이스 프로바이더이다. 
EF Core는

.NET 개발자들이 데이터베이스와 상호 작용할 수 있는 인기 있는 Object-Relational Mapping (ORM) 프레임워크이다.
이것은 높은 수준에서 데이터베이스 작업을 추상화하므로 개발자들이 SQL 쿼리를 직접 작성하는 것보다는
객체 지향적인 방식으로 데이터베이스를 다룰 수 있게 해준다.

Npgsql.EntityFrameworkCore.PostgreSQL 패키지를 프로젝트에 추가하면, 
개발자는 EF Core를 사용하여 PostgreSQL 데이터베이스와 상호 작용할 수 있다.
이것은 데이터베이스 연결, 쿼리 실행, 데이터베이스 스키마 마이그레이션 등의 작업을 단순화하고 자동화할 수 있다.

 

appsetting.Development.json 에 Postgres 추가

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "AppDbContext": "Host=localhost;Database=APP_DEV;Username=postgres;Password=nse#01;Port=5432;"
  }
}

 

AddDbContext 에 postgres 연결

Program.cs 에서 빌더 서비스에 AddDbContext를 등록하는 부분을 아래와 같이 변경한다.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(opt => opt.UseNpgsql(builder.Configuration.GetConnectionString("AppDbContext")));

물리적 DB 생성

이제 docker에 있는 postgres DB를 연결하였으므로,
설계한 모델 Todo.cs를 EF Core를 사용하여 postgres DB에 엔터티를 생성한다.
엔터티를 생성하기 위해 현재 시점에 등록된 DBSet을 기준으로
마이그레이션을 하고 DB에 업데이트를 해야한다.
 

마이그레이션 관련 패키지 설치

마이그레이션 코드를 생성하기 위해 패키지를 설치한다.

dotnet add package Microsoft.EntityFrameworkCore.Design

마이그레이션 추가 / 디비 반영

마이그레이션 코드를 작성하고 DB에 업데이트 한다.

dotnet ef migrations add firstCreation -c AppDbContext
dotnet ef database update -c AppDbContext

 빌드 런 후 Postman 으로 디비 저장 확인

dotnet build
dotnet run


여기까지의 내용이
기본적인 Minimal API 를 생성하면서,
모델을 구성하였고, EF Core의 ORM 을 사용하기 위해 DB Context 클래스를 만들고 DBSet에 설계한 모델을 등록하였다.
Code first 라는 방식으로 코드로 데이터베이스 엔티티화 할 코드를 작성하였고,
EF Core의 디자인 패키지를 설치하여 
현재 시점의 모델을 기준으로 마이그레이션 하여 디비에 업데이트해주었다.
 


EF 란?

Entity Framework (EF) Core는 .NET 기반의 개발자들을 위한 객체 관계 매핑(ORM) 프레임워크이다.


Minimal API와 MVC API의 차이
더보기

MVC (Model-View-Controller) API

MVC는 Model-View-Controller의 약자로,

데이터, 사용자 인터페이스, 데이터와 사용자 인터페이스를 제어하는 로직을 분리하는 방법이다.

ASP.NET MVC API는 이 패턴을 웹 개발에 적용한다.

  • Model : 데이터와 비즈니스 로직을 표현하는 구성요소이다.
  • View : 사용자에게 보여지는 UI를 표현하는 구성요소이다.
  • Controller : Model 과 View 사이의 인터랙션을 관리하는 구성요소이다.

MVC 패턴은 코드를 잘 구조화하고 분리하게 하므로 유지보수성과 확장성을 향상시킨다.

 

Minimal API

Minimal API는 ASP.NET Core 6.0에서 도입된 새로운 기능으로, 

가능한 가장 적은 양의 코드로 API를 만들 수 있게 해준다.

Minimal API는 기능적으로 MVC API와 유사하지만, 훨씬 더 간결하게 API를 생성할 수 있다는 장점이 있다.

Minimal API 는 기본적으로 함수를 중심으로 작동하고, 간결하다.

특히 프로토 타이핑이나 작은 서비스에 적합하다.

하지만 이러한 간결함은 때때로 복잡한 시나리오나 큰 규모의 프로젝트에서의 유지보수성을 약간 떨어뜨릴 수 있다.

 

결론적으로 MVC API와 Minimal API 의 가장 큰 차이점은 코드의 복잡성과 유지보수성이고,

서버에서 페이지를 생성하는 경우, MVC API를 기준으로 설계하면 된다.

오로지 페이지는 클라이언트에서 생성한다면 Minimal API 로 설계하면 좋다.

MVC API는 구조화된 패턴을 따르므로 복잡한 애플리케이션에 적합하고,

Minimal API 는 간결하고 빠른 개발을 위해 설계되었다.


인터랙션?
더보기

일반적으로 서로 다른 두 개체나 시스템 간의 상호작용을 말한다.

이는 정보의 교환, 데이터의 전송, 이벤트의 트리거 등 다양한 형태로 이루어진다.

소프트웨어나 웹 개발의 맥락에서, 

인터랙션은 일반적으로 사용자가 시스템과 상호 작용하는 방식을 지칭한다.

예를 들어, 사용자가 버튼을 클릭하거나 텍스트를 입력하거나, 스크롤하는 등의 행동을 취하면

이러한 행동들이 시스템에 의해 해석되고 반응하는 과정을 인터랙션이라고 한다.

 

MVC (Model-View-Controller) 패턴에서 인터랙션은

일반적으로 사용자의 액션(버튼클릭, 데이터 입력 등)과 같은

시스템 반응 (데이터베이스 업데이트, 새 페이지 표시 등) 사이의 상호작용을 말한다.

여기서 컨트롤러(Controller) 가 이런 인터랙션을 관리하는 역할을 한다.


애자일(Agile) 과 Minimal API
더보기

애자일은 소프트웨어 개발 방법론 중 하나로,

반복적이고 점진적인 개발 방식을 통해 프로젝트의 유연성을 높이고 변화에 빠르게 대응하도록 설계되었다.

애자일 개발은 주요 기능부터 시작해서 이를 지속적으로 개선하고 확장하는 방식으로 진행된다.

이는 고객의 피드백을 바탕으로 실시간으로 제품을 조정하고 개선하는데 효과적이다.

ASP.NET Core 의 Minimal API는 애자일 방법론과 잘 어울린다.

Minimal API는 매우 간결한 코드로 빠르게 API를 개발하고 배포할 수 있도록 해주므로,

애자일 개발에서는 선호하는 반복적이고 점진적인 개발 방식에 잘 맞다.

 

예를들어,

애자일 개발 팀은 Minimal API를 사용하여 초기 버전의 웹 서비스를 빠르게 구현하고 배포할 수 있다.

이 초기버전은 주요 기능만을 포함할 수 있다.

그런 다음 팀은 이 초기 버전을 바탕으로 사용자의 피드백을 수집하고

이 피드백을 반영하여 웹 서비스를 점진적으로 개선하고 확장할 수 있다.


dotnet build 와 dotnet run 에 대해서..
더보기

 dotnet build 와 dotnet run은 .NET Core 에서 제공하는 커맨드 라인 명령어 이다.

 

  • dotnet build
    • 프로젝트의 소스 코드를 컴파일하여 실행 가능한 바이너리 형태의 출력물을 생성한다. (.dll)
      출력물을 생성할 때 다음과 같은 단계를 거친다. 
      • 복원 (restore) : 필요한 종속성들(dependencies)를 가져온다. 이 작업은 프로젝트가 참조하는 모든 NuGet 패키지를 다운로드 한다.
      • 컴파일(compile) : 소스 코드를 MSIL (Microsoft Intermediate Language) 코드로 컴파일 한다. 이 중간 코드는 .NET Runtime 에서 실행될 수 있다.
      • 링크(link) : 컴파일된 코드와 필요한 라이브러리들을 연결한다. 그 결과로 실행 가능한 프로그램이 만들어진다.
  • dotnet run
    • dotnet build를 호출한 후, 빌드하여 생성된 실행파일을 실행한다. 
EF Core에 대해
더보기

Entity Framework Core (EF Core)는 

.NET Core와 .NET 5/6+를 위한 오픈소스 객체-관계 매핑 (Object-Relational Mapping, ORM) 프레임워크이다.

.NET에서 데이터베이스와 상호 작용하는데 사용되는 인기 있는 Object-Relational Mapping (ORM) 프레임워크라고 할 수 있다.

 

EF Core는 SQL 쿼리를 직접 작성하는 대신 고수준의 객체 지향 프로그래밍 방식을 사용할 수 있다.


EF Core의 주요 기능

  • LINQ 쿼리 지원
    • EF Core는 LINQ(Language Integrated Query)를 지원하여 데이터베이스로의 쿼리 작성을 단순화시킨다.
      LINQ를 사용하면, 쿼리를 강력하고 유연하게 작성할 수 있다.
  • 데이터베이스 스키마 마이그레이션
    • EF Core는 코드 기반 마이그레이션을 지원한다.
      데이터베이스 스키마 변경사항을 C# 코드로 관리하고, 이를 통해 데이터베이스를 버전 관리할 수 있다.
  • 데이터베이스 공급자 간 이식성
    • EF Core는 다양한 데이터베이스 공급자를 지원한다.
      예를 들어, Microsoft SQL Server, SQLite, PostgreSQL, MySQL 등 다양한 데이터베이스 시스템을 사용할 수 있다. 이를 통해 같은 코드를 다른 데이터베이스 시스템에서도 실행할 수 있다.
  • 변경 추적
    • EF Core는 작업 중인 객체의 상태를 추적하고, 이를 데이터베이스와 동기화하는 기능을 제공한다.
  • Identity Resolution
    • 같은 키를 가진 개체가 여러번 로드되면,
      EF Core는 이를 하나의 개체 인스턴스로 유지하여 동일성(identity)을 보장한다.

EF Core는 이런 기능을 제공함으로써 데이터 액세스 계층의 개발을 단순화하고 표준화하는 데 도움을 준다.

AppDbContext.cs 코드 설명
더보기
  • using Microsoft.EntityFrameworkCore;
    • Microsoft의 Entity Framework Core 라이브러리를
      이 코드 파일에서 사용할 수 있도록 하는 데 필요한 using 지시문임.
  • public class AppDbContext : DbContext
    • `AppDbContext`라는 클래스를 정의하고, `DbContext`를 상속받는다.
    • `DbContext` 클래스는 Entity Framework Core에서 제공하며, 데이터베이스와의 상호작용을 관리한다.
  • `public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)`
    • `AppDbContext`의 생성자
    • `DbContextOptions<AppDbContext>` 인스턴스를 매개변수로 받고,
      이를 기반 클래스 `DbContext`의 생성자로 전달한다.
    • `DbContextOptions`는 컨텍스트 구성에 대한 옵션을 제공하며,
      데이터베이스 공급자 선택, 연결 문자열 설정 등의 정보를 포함한다.

  • `public DbSet<Todo> Todos { get; set; }`
    • `DbSet<T>`은 Entity Framework Core에서 제공하는 클래스로,
      특정 유형의 엔티티에 대한 쿼리를 수행할 수 있는 메서드를 제공한다.
    • `Todo` 타입의 엔티티를 위한 `DbSet`을 `Todos`라는 이름으로 정의하고 있다.
    • `DbSet` 인스턴스는 `Todo` 엔티티를 위한 데이터베이스 테이블을 나타낸다.

 

이 코드는 Entity Framework Core를 이용해 데이터베이스와 연동하는 `AppDbContext`라는 클래스를 정의하고

`Todo` 엔티티에 대해 데이터베이스 연산을 수행할 수 있게 한다.

API 코드에서 DI 관련 정리
더보기

위의 코드에서는 Dependency Injection (DI) 패턴이 사용되고 있다.

DI는 객체 간의 의존 관계를 외부에서 설정해주는 디자인 패턴으로,

코드의 결합도를 낮추고 테스트 가능성을 높이는 데 도움을 준다.

.NET Core에서 DI 컨테이너를 이용해 서비스를 등록하고 필요한 곳에서 주입받아 사용할 수 있다.

이는 `IServiceCollection` 인터페이스를 이용해 가능하며, 이 인터페이스는 애플리케이션의 서비스 컬렉션을 나타낸다.

`builder.Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("TodoList"));` 에서

AddDbContext<AppDbContext>는 `AppDbContext` 인스턴스를 DI(Dependency Injection) 컨테이너에 등록하고 있다.

이를 통해 `AppDbContext`가 필요한 다른 클래스나 메서드에 대해,

프레임워크의 DI 컨테이너가 알아서 적절한 `AppDbContext` 인스턴스를 자동으로 주입해준다.

 

예를 들어, 어떤 서비스 클래스가 `AppDbContext`에 의존하고 있다면 

이 클래스를 테스트할 때, 실제 데이터베이스에 연결하는 대신 메모리 내 데이터베이스나 모의 객체를 주입할 수 있다.

따라서 `builder.Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("TodoList"));` 코드는 `AppDbContext`의 인스턴스를 생성하고 이를 DI 컨테이너에 등록하며, 

이 인스턴스가 메모리 내 데이터베이스를 사용하도록 설정하는 역할을 한다.

이후에는 `AppDbContext`가 필요한 곳에서 이를 요청하면,

.NET Core가 알아서 DI 컨테이너에서 해당 인스턴스를 가져와 주입해준다.

또한, 각 HTTP 메서드 핸들러에서 `AppDbContext db`라는 파라미터를 이용하고 있다.

이 파라미터는 DI를 통해 주입받은 `AppDbContext` 인스턴스를 나타낸다.

즉, HTTP 요청을 처리할 때마다 `AppDbContext` 인스턴스가 필요하며, 이 인스턴스는 DI 컨테이너에서 자동으로 제공받는다.

이런 식으로 DI 패턴을 사용하면 코드의 모듈성과 유연성을 향상시킬 수 있다. 

또한, 코드 테스트 시 특정 서비스를 Mock 객체 등으로 대체하기 쉬워져 단위 테스트의 용이성을 높일 수 있다.

async await
더보기

.NET에서 async/await 패턴은 비동기 프로그래밍을 단순화하고 가독성을 높이는 강력한 기능이다.

이 패턴은 CPU를 매우 효율적으로 사용하면서도 블로킹 없이 복잡한 I/O 작업을 수행할 수 있게 해준다. 

비동기 메서드는 'async' 키워드를 사용해 선언되며, 일반적으로 'Task'나 'Task<T>'를 반환한다.

'await' 키워드는 이러한 비동기 메서드 내에서 사용되며, Task의 완료를 기다린다.

  • async 키워드
    • 메서드, 람다 표현식 또는 익명 메서드에 'async' 키워드가 포함되면 해당 메서드가 비동기적으로 실행된다.
    • 비동기 메서드는 메서드 내에서 'await' 키워드를 사용할 수 있는 메서드를 의미한다.
  • await 키워드
    • 'await' 키워드는 비동기 메서드 내에서 사용되며, 비동기 작업의 완료를 대기하는 데 사용된다.
    • 'await' 표현식은 해당 작업이 완료될 때까지 실행을 일시 중지한다.

 


이 패턴의 작동 원리를 이해하려면, 

'async' 메서드가 호출되면 CLR(Common Language Runtime)이 메서드를 호출하는 측에 반환되고, 

메서드 내에서 'await' 연산자에 도달하면 실행이 일시 중단되며, 

제어는 다시 호출자에게 반환되는 것을 알아야 한다.

비동기 작업이 완료되면 'await' 표현식이 완료되고, 그 후의 코드가 실행된다.

이렇게 되면 비동기 메서드 내에서 다음 'await' 표현식이 나타날 때까지 또는 메서드가 완료될 때까지 코드 실행이 계속된다. 
메서드의 실행을 'await' 표현식이 완료되기를 기다리는 동안 일시 중단할 수 있으므로,

해당 스레드는 다른 작업을 수행할 수 있게 되어 애플리케이션의 전반적인 응답성이 향상된다.

추가로 이해를 돕기 위해, awaitable 작업들은 보통 I/O 바운드 작업이나 네트워크 요청 같은 것들을 수행하게 된다.

이런 작업들은 CPU가 블로킹 되어있지 않아도 결과를 기다리는 시간이 필요하다.

따라서 async/await는 이런 대기 시간 동안 CPU가 다른 작업을 수행할 수 있게 하여 자원을 효율적으로 활용하게 한다.

블로킹
더보기

컴퓨팅에서 프로세스 또는 스레드가 실행을 중지하고 특정 조건이 충족될 때까지 대기하는 상태를 가리킨다.

이런 블로킹 상황은 대개 I/O 작업, 네트워크 통신, 락(lock) 획득 등에 의해 발생한다.

예를 들어, 프로그램이 디스크로부터 데이터를 읽는 동안, 

해당 I/O 작업이 완료될 때까지 프로그램의 실행이 일시적으로 중단되면 이를 "블로킹 I/O"라고 한다.

동일하게, 프로그램이 네트워크에서 데이터를 받아올 때 데이터 수신이 완료될 때까지 대기하는 것도 "블로킹" 상태라고 한다.

블로킹 상태에서 프로그램은 다른 작업을 수행하지 못하고 해당 조건이 충족될 때까지 기다리게 된다. 

이런 블로킹 상태는 프로그램의 성능과 반응성에 부정적인 영향을 미칠 수 있다.

이를 해결하기 위한 방법 중 하나로 "비동기 프로그래밍"이다.

비동기 프로그래밍에서는 특정 작업이 완료될 때까지 기다리는 대신, 

작업을 시작하고 그 결과를 나중에 처리하도록 코드를 작성한다. 

이렇게 하면 프로그램이 작업의 완료를 기다리는 동안 다른 유용한 작업을 수행할 수 있게 되므로, 

블로킹으로 인한 프로그램의 반응성 저하를 최소화할 수 있다.

EF Core Design 패키지에 대해서..
더보기

Microsoft.EntityFrameworkCore.Design 패키지는

Entity Framework Core (EF Core)에서 설계 시 관련 기능을 제공하는 패키지이다.

Microsoft.EntityFrameworkCore.Design 패키지는 주로 다음과 같은 두 가지 주요 기능을 제공한다.

  • Code First Migrations
    • 데이터베이스 스키마를 C# 코드를 통해 정의하고 이러한 정의에 따라 데이터베이스 마이그레이션을 자동으로 생성하고 적용할 수 있다.
  • Database Reverse Engineering
    • 기존 데이터베이스의 스키마를 기반으로 C# 엔티티 클래스를 생성할 수 있다.
      이를 "Database First" 접근법이라고도 한다.

 

이 패키지를 프로젝트에 추가하면,

EF Core를 사용하여 데이터베이스를 효과적으로 설계하고 관리하는 데 필요한 도구와 기능을 사용할 수 있다.


Code first 접근법
더보기

Entity Framework (EF) Core의 Code First 접근법은
데이터베이스 모델링을 프로그래밍 코드를 통해 직접하는 방식이다.
데이터베이스의 테이블과 관계를(스키마) 명시적으로 .NET 클래스(C# 클래스)로 정의하고,

EF Core는 이러한 클래스에 기반하여 데이터베이스 스키마를 생성하거나 변경한다.

Code First 접근법의 주요 과정은 아래와 같다.

모델 클래스 생성
처음에는 애플리케이션에서 사용할 모든 엔티티 클래스를 생성한다.
각각의 클래스는 데이터베이스 테이블을 나타내며,
클래스의 속성은 테이블의 열과 매핑된다.

클래스 간의 관계를 표현하기 위해 네비게이션 속성도 사용할 수 있다.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public List<Product> Products { get; set; }
}

위의 코드에서 Product 클래스와 Order 클래스가 있다.

Product는 Id, Name, Price 속성을 가지고 있고

Order는 Id, OrderDate, Products 속성을 가지고 있다.

Order 클래스는 Product 와 1:N 관계를 가지고 있다. 



DBContext 클래스 생성
EF Core의 Code First 접근 방식에서

위에서 설계한 모델 클래스를 기반으로 데이터 베이스를 생성할 수 있다.

그렇게 하기위해 데이터 베이스 컨텍스트 클래스를 작성하는 것이다.

데이터 베이스 컨텍스트 클래스는 EF Core와 데이터베이스 간의 중간계층으로 작동하며

EF Core의 핵심 클래스로서, 데이터베이스와의 모든 상호 작용을 관리한다.
이 클래스 내에서는 데이터베이스에 있는 테이블을 나타내는 DbSet 프로퍼티를 정의한다.

public class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True");
    }
}

 


데이터베이스 마이그레이션
마이그레이션은 데이터베이스 스키마를 생성하거나 업데이트하는 프로세스이다.

EF Core의 마이그레이션 기능을 사용하여 C# 클래스의 변경 사항을 감지하고,

데이터베이스 스키마를 생성하거나 업데이트할 수 있다.

마이그레이션은 코드에서 데이터베이스 스키마로의 변경을 추적하고 관리하는데 도움을 준다.
이는 주로 Add-Migration및 Update-Database명령을 사용하여 수행된다.

Add-Migration InitialCreate
Update-Database


데이터 조작
생성된 DBContext와 모델 클래스를 이용하여
데이터베이스에 CRUD (Create, Read, Update, Delete) 작업을 수행할 수 있다.

using (var db = new MyDbContext())
{
	db.Blogs.Add(new Blog { Url = "http://sample.com" });
    db.SaveChanges();
}


이러한 방식을 사용하면

데이터베이스 스키마를 직접 작성하거나 유지 관리할 필요 없이

C# 클래스를 통해 데이터베이스 구조(스키마)를 손쉽게 관리할 수 있으며,

더욱 객체지향적인 코드에 집중할 수 있다.

버전 관리 시스템을 통해 데이터베이스 변경 사항을 추적하는 데 도움이 될 수 있다.


DataBase First
더보기

Database First 는 Code First 접근법과 반대되는 개념이다.

Database First 접근법에서는 

개발자가 먼저 데이터베이스 스키마를 직접 또는 다른 데이터베이스 디자인 도구를 사용하여 만든다.

그런 다음 EF Core 툴을 사용하여 이 데이터베이스 스키마에서 모델 클래스와 DBContext를 생성한다.

즉, Code First 접근법에서는 클래스를 먼저 작성하고 이를 기반으로 데이터베이스를 생성하는 반면, 

Database First 접근법에서는 데이터베이스를 먼저 생성하고 이를 기반으로 클래스를 생성한다.

Database First 접근법은 기존 데이터베이스에 대한 코드를 작성할 때 특히 유용하다.

이미 만들어진 데이터베이스의 구조를 그대로 반영한 모델을 생성할 수 있으므로, 

개발 초기 단계에서 시간을 절약할 수 있다.