Linux에서 .NET Aspire에 DELETE 엔드포인트 추가하기
[[academy-video-youtube({"vid": "x10CYBXrLxg", "start_time": "0", "title": "리눅스에서 .NET Aspire에 DELETE 엔드포인트 추가", "creator": "Tim Corey", "length": "8m 40s"})]]
모든 CRUD API는 결국 레코드를 제거하는 방법이 필요하며, DELETE 동사는 GET, POST 및 PUT과 함께 네 가지 주요 HTTP 작업을 완료합니다. 다른 동사와 비교했을 때, DELETE는 구조적으로 가장 간단합니다: 요청 본문도 없고, 검증 파이프라인도 없으며, 복잡한 반환 유형도 없습니다. 새로운 질문을 소개하는 것은, 존재하지 않는 레코드 삭제 요청에 대해 무엇을 반환할지에 대한 디자인 질문이며, 이는 보편적으로 맞는 답이 없습니다.
그의 비디오 "Adding a DELETE Endpoint in .NET Aspire on Linux"에서 Tim Corey는 Tiny Ticket API의 마지막 엔드포인트 추가를 마무리하며, GET-by-ID 저장 프로시저에서 조용히 있던 매개변수 대소문자 불일치를 수정하고, 누락된 레코드에 대해 404와 204 중 언제 반환할지에 대해 논의합니다. 에피소드에서는 C# on Linux 시리즈의 다음 단계의 초점이 되는 프론트엔드로의 전환도 미리 보여줍니다. 시리즈를 따라왔거나 Minimal API에서 DELETE를 처음 연결하고 있다면, 이 기사는 전체 엔드포인트와 프로젝트 전반에 매개변수 바인딩을 일관되게 만든 작은 리팩터를 안내합니다.
DELETE 엔드포인트 매핑
[1:02 - 2:14] 엔드포인트 등록은 두 가지 조정이 적용된 다른 라우트와 같은 형태를 따릅니다. 해당 경로에는 {id:int} 세그먼트가 포함되어 있어 본문이 아닌 URL에 ID가 전달되며, 핸들러 서명에는 MapDelete 대신 MapPost 또는 MapPut를 사용합니다. 식별자 외에 필요한 것은 없으므로 입력 레코드가 없습니다.
app.MapDelete("/api/tickets/{id:int}",
async Task<Results<NoContent, ValidationProblem>>
(ISqlDataAccess sql, int id) =>
{
await sql.SaveDataAsync("dbo.spTickets_Delete",
new { Id = id }, "TicketDB");
return TypedResults.NoContent();
});app.MapDelete("/api/tickets/{id:int}",
async Task<Results<NoContent, ValidationProblem>>
(ISqlDataAccess sql, int id) =>
{
await sql.SaveDataAsync("dbo.spTickets_Delete",
new { Id = id }, "TicketDB");
return TypedResults.NoContent();
});핸들러는 Dapper 래퍼를 통해 spTickets_Delete 저장 프로시저를 호출하고, 익명 객체와 함께 ID를 전달합니다. TypedResults.NoContent()를 반환하면 204 상태가 생성되어 작업이 성공했음을 알리며 반환할 응답 본문이 없음을 나타냅니다. 반환 유형 선언은 이전 에피소드의 PUT 엔드포인트와 유사하며, 두 작업 모두 프레임워크 관점에서 같은 결과 세트를 가집니다.
매개변수 대소문자 불일치 수정
[2:14 - 4:32] DELETE 호출을 연결하는 동안, Tim은 시리즈 초기에 소개한 불일치를 발견합니다. spTickets_Delete 저장 프로시저는 대문자 Id 매개변수를 사용하므로, 익명 객체에는 명시적인 Id = id 할당이 필요합니다. spTickets_Update 프로시저도 대문자 Id를 사용합니다. 하지만 spTickets_Get, GET-by-ID 엔드포인트에 대한 프로시저는 소문자 id를 사용합니다. 이 소문자 변형은 원래 핸들러가 명시적인 할당 없이 new { id }를 통과시킬 수 있게 했으며, 당시에 편리하게 느껴졌지만 코드베이스는 일관성이 없게 되었습니다.
비대칭성을 계속 유지하는 대신, 그는 SQL Server Management Studio를 열고 GET 프로시저를 대문자 Id를 사용하도록 수정합니다:
ALTER PROCEDURE spTickets_Get
@Id int
AS
BEGIN
SELECT Id, Title, Description, DateCompleted, Priority, CreatedDate
FROM dbo.Tickets
WHERE Id = @Id;
END프로시저가 업데이트되면, Program.cs의 GET 핸들러는 이제 new { id }에서 new { Id = id }로 변경하여 DELETE 및 PUT 핸들러가 사용하는 것과 동일한 명시적인 매핑이 필요합니다. 변경은 기계적이지만, 그 논리는 중요합니다: 저장 프로시저 전반의 일관된 매개변수 대소문자는 모든 엔드포인트가 같은 방식으로 매개변수 바인딩을 하며 데이터 접근 계층을 읽을 때 생길 수 있는 작은 혼란을 제거합니다. 네 군데 중 한 곳에서만 적용되는 관례는 관례가 아닙니다.
누락된 레코드에 대해 204와 404 중 어느 것을 반환할지
[4:46 - 5:46] 엔드포인트가 컴파일되면, Tim은 모든 DELETE 구현에서 발생하는 디자인 질문을 잠시 멈춥니다. 호출자가 존재하지 않는 ID를 전달하면 API는 무엇을 반환해야 합니까? 합리적인 두 가지 대답이 있습니다.
행이 삭제되었든 실행하는 것을 무관하게 204 NoContent를 반환함으로써 요청을 멱등하게 처리합니다. 호출자의 관점에서 리소스는 사라졌으며, 그것이 목표입니다. 현재 핸들러는 그렇게 하고 있으며, Tiny Ticket 프로젝트는 그것으로 출하될 것입니다. 삭제된 행을 실제로 삭제했는지 여부를 보고하기 위해 행 수를 반환하는 저장 프로시저를 요구함으로써 누락된 레코드에 대해 404 NotFound를 반환하면 호출자에게 더 많은 정보를 제공합니다.
프론트엔드가 이미 어떤 ID가 존재하는지 알고 있는 내부 CRUD API의 경우(목록을 방금 불러왔기 때문에) 204가 문제 없으나, 외부 API의 경우, 호출자가 ID를 추측할 수 있는 경우에는 데이터가 없을 때 번지르르한 환상을 방지합니다. Tim은 404를 반환함으로써 데이터베이스에 존재하는 ID에 대한 정보를 누출할 수 있음을 언급합니다. 그러나 삭제 작업의 경우 해당 엔드포인트를 실행하려면 이미 작성 권한이 필요하기 때문에 실제적인 위험은 낮습니다.
Swagger를 통한 테스트
[5:46 - 7:08] 데이터베이스가 실행 중일 때, Tim은 API를 실행하고 Swagger를 엽니다. 그는 현재 데이터를 파악하기 위해 모든 레코드를 가져오기로 시작합니다: 원래 초기값인 1, 2, 3 레코드와 초기 삽입 테스트에서 남은 107, 109, 110 레코드.
삭제를 107에 적용하고 204를 받습니다. 110과 마찬가지입니다. 누락된 레코드 동작을 확인하기 위해, 데이터베이스에 존재하지 않았던 ID 1011에 대해 DELETE를 실행합니다. 응답은 여전히 204이며, 삭제된 것이 없다는 것을 나타내지 않습니다. 이것이 이전 섹션에서 논의된 교환이며, 이제 실제 API 응답에서 볼 수 있습니다.
두 번째 모든 레코드 가져오기를 실행하면 최종 상태를 확인합니다: 레코드 1, 2, 3과 109가 남아 있습니다. DELETE 엔드포인트는 유효한 ID에 대해서는 작동하며, 구현된 대로 잘못된 ID에 대해서는 조용히 실패합니다.
정리: CRUD 완료
[7:08 - 8:38] DELETE를 추가하면 Tiny Ticket API의 네 가지 CRUD 동사가 완료됩니다. 같은 구조적 패턴이 모든 엔드포인트에 적용됩니다: 라우트 정의, 저장 프로시저 이름, Dapper 데이터 액세스 호출, 형식화된 결과. Tim은 실제 API는 아마도 더 많은 엔드포인트를 추가할 것이라고 솔직하게 언급합니다. 예를 들어 전체 객체를 전송하지 않고 티켓을 완료로 표시하기 위한 PATCH나 전용 검색 엔드포인트. 하지만 이 시리즈의 목표는 각 레이어를 집중시켜 다음 레이어(프론트엔드)가 호출할 깨끗한 표면을 가지도록 하는 것입니다.
일관성은 프로젝트를 읽기 쉽게 만드는 요소입니다. Dapper 래퍼, POST 삽입 패턴, 검증 파이프라인, 형식화된 결과 모두가 결합되어 새 엔드포인트마다 어떤 동사를 구현하는지와 관계없이 비슷한 양의 코드가 필요합니다. 이러한 예측 가능성은 다음에 나오는 프론트엔드 작업의 쾌적한 목표를 만드는 요소입니다.
결론
[8:38 - 8:40] 최소 API에 DELETE 엔드포인트를 추가하려면 경로에 ID 세그먼트와 데이터 액세스 래퍼를 통한 저장 프로시저 호출, 그리고 TypedResults.NoContent() 반환이 필요합니다. 이 엔드포인트는 Tiny Ticket 프로젝트의 CRUD 표면을 완성하며, 다음 단계의 시리즈에서 프론트엔드로의 전환을 준비합니다.
시리즈 내비게이션: 이 기사는 Linux에서 Tiny Ticket 앱을 구축하는 C# 시리즈의 일부입니다. 이전: Adding a PUT Update Endpoint. 다음 단계: API를 소비하는 프론트엔드 페이지.
예시 팁: 저장 프로시저를 변경하지 않고 더 유익한 DELETE 응답을 원한다면, SaveDataAsync에서 영향을 받은 행 수를 캡처하고 그 수가 0일 때 TypedResults.NotFound()를 반환하세요. 이는 데이터 액세스 레이어를 재구성하지 않고 404 경로를 추가합니다.
YouTube 채널에서 전체 비디오를 보고 Linux 시리즈의 C#에서 CRUD 엔드포인트 구축에 대한 더 많은 통찰력을 얻으세요.

