Programming
-
[gRPC] C# gRPC 서버(Service) 샘플 만들어보기2023.01.09
-
[gRPC] C# 프로토콜 버퍼 사용하기2023.01.06
-
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기.2023.01.04
-
[gRPC] gRPC 알아보기2023.01.02
-
[DI] 의존성 관리하기 (2)2019.12.25
-
[DI] 의존성 관리하기 (1)2019.12.17
[gRPC] C# gRPC 서버(Service) 샘플 만들어보기
들어가며
gRPC에 대한 내용을 간단하게 정리해봤으나, 실질적으로 동작하는 부분까지 확인을 직접 해보는게 좋을거 같아서 직접 프로젝트를 구성해봤습니다.
요즘의 서버는 다양한 개발환경으로 구성이 되어 있는데요. 각 서버간의 통신에 RESTFul API를 이용해서 구성하기도 하지만, gRPC를 이용해서 보다 빠른 환경으로 구성할 수 있을 거 같네요.
Service 프로젝트 만들기
새로운 프로젝트를 만들때, gRPC 서비스 템플릿을 이용해서 프로젝트를 생성 할 수 있습니다.
이후 기본적인 위치 및 프로젝트 이름을 생성해서 프로젝트를 만들 수 있습니다.
Client 프로젝트 만들기
클라이언트 프로젝트의 경우에는, 콘솔 어플리케이션 템플릿으로 생성 하였습니다.
이후, 아래의 내용을 참고해서 Nuget Package 를 설치해주시면 됩니다.
.proto 파일에 대한 C# 도구 지원
.proto 파일에서 C# 자산을 생성하려면 도구 패키지 Grpc.Tools가 필요합니다. 생성된 자산(파일)과 관련해서 다음 사항을 확인합니다.
- 프로젝트를 빌드할 때마다 필요에 따라 생성됩니다.
- 프로젝트에 추가되거나 소스 제어에 체크 인되지 않습니다.
- obj 디렉터리에 포함된 빌드 아티팩트입니다.
서버 프로젝트의 경우에는 Grpc.AspNetCore 패키지가 포함되어 있고, 해당 패키지 안에 Grpc.Tools 에 대한 참조가 포함되어 있어서, 별도로 추가 할 필요는 없습니다.
클라이언트 프로젝트의 경우에는 Grpc.Tools 가 따로 포함이 되어 있지 않기 때문에 Nuget.Package 에서 위 내용을 검색 후 설치해 주시면 됩니다.
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools
아니면 패키지 관리자 콘솔에서, Install-Package 해주세요~
Client 프로토콜 파일 추가
syntax = "proto3";
option csharp_namespace = "GrpcGreeterClient";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
서버에서 정의한 proto 파일을 클라이언트에도 정의를 해줍니다.
namespace 는 클라이언트에서 사용하도록 변경해주고요. 다른 부분은 동일하게 정의 합니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.7" />
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.49.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
</Project>
Client에서 생성한 파일은 빌드에 포함되지 않다보니, 추가적으로 프로젝트를 마우스 오른쪽 단추로 클릭하고 프로젝트 파일 편집을 선택합니다.
해당 파일을 Protobuf 속성을 사용해서 Include 해줍니다.
클라이언트 코드 추가
using System;
using System.Threading.Tasks;
using Grpc.Net.Client;
using GrpcGreeterClient;
namespace GrpcSampleClient
{
class Program
{
static async Task Main(string[] args)
{
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
클라이언트 코드는, gRPC 서버에 SayHello Method를 호출하도록 구성하였으며, Request 로 "GreeterClient" 를 인자로 넘기도록 구성된 샘플 코드를 사용하였습니다.
그리고 Response 로 받은 내용을 찍어주는 심플한 코드입니다.
서버 코드 추가
syntax = "proto3";
option csharp_namespace = "GrpcSampleService";
package greet;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings.
message HelloReply {
string message = 1;
}
서버쪽 proto 파일입니다. namespace를 제외하고는 동일합니다.
using Grpc.Core;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace GrpcSampleService
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
서버쪽 코드는 샘플 예제를 참조하였으며, Request 로 들어온 이름에 "Hello " 텍스트를 붙여서 Response 해줍니다.
결과
gRPC 서버를 먼저 시작해줍니다.
이후 클라이언트를 실행했으며, 서버로 부터 받은 Response 데이터를 정상적으로 출력해주고 있습니다.
서버쪽으로도, 요청이 들어온 Log가 정상적으로 보여지네요.
간단한 샘플 코드를 이용해서, 다양하게 활용하면 좋을 거 같습니다.
END
'Programming' 카테고리의 다른 글
[gRPC] C# 프로토콜 버퍼 사용하기 (0) | 2023.01.06 |
---|---|
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기. (0) | 2023.01.04 |
[gRPC] gRPC 알아보기 (0) | 2023.01.02 |
[DI] 의존성 관리하기 (2) (0) | 2019.12.25 |
[DI] 의존성 관리하기 (1) (0) | 2019.12.17 |
[gRPC] C# 프로토콜 버퍼 사용하기
protobuf-net 이란?
protobuf-net은 .NET 코드용 계약 기반 직렬 변환기로, Google에서 설계한 "프로토콜 버퍼" 직렬화 형식으로 데이터를 작성합니다. 그러나 API는 Google과 매우 다르며 일반적인 .NET 패턴을 따릅니다. (사용 면에서 XmlSerializer, DataContractSerializer, 등) 표준 유형을 작성하고 특성을 사용할 수 있는 대부분의 .NET 언어에서 작동해야 합니다.
.proto 파일을 생성 하지 않아도 됩니다.
NuGet 패키지 다운로드
protobuf-net 은 Nuget Package 관리자를 통해서 다운로드 받을 수 있습니다.
제가 받았을때 버전은 3.1.17 이였네요.
사용방법
[ProtoContract]
public class Account
{
[ProtoMember(1)]
public string UserID { get; set; }
[ProtoMember(2)]
public int Age { get; set; }
public int Num { get; set; }
}
사용방법은 크게 어렵지 않았습니다. 사용하려는 프로토콜 Class 에 ProtoContract 라는 Attribute를 선언해주고,
각각의 Property에 ProtoMember Attribute를 추가적으로 선언해주면 됩니다.
인덱스는 1 번 부터 시작합니다.
static void Main(string[] args)
{
Account account = new Account
{
UserID = "Martin",
Age = 1337,
Num = 1
};
// serialize
MemoryStream serialize = new MemoryStream();
ProtoBuf.Serializer.Serialize<Account>(serialize, account);
byte[] byteData = serialize.ToArray();
Console.WriteLine($"Serialize : {BitConverter.ToString(byteData)}");
//Console.WriteLine($"Json : {}")
// deserialize
MemoryStream deserialize = new MemoryStream(byteData);
Account result = ProtoBuf.Serializer.Deserialize<Account>(deserialize);
Console.WriteLine($"DeSerialize : {result.UserID}, {result.Age}, {result.Num}");
Console.ReadLine();
}
간략하게 Serialize, Deserialize 하는 방법입니다.
protobuf-net 을 사용해서 C# 내에서는 사용방법이 간략하네요.
참조
https://dotnetcoretutorials.com/2022/01/13/protobuf-in-c-net-part-2-serializing-deserializing/
END
'Programming' 카테고리의 다른 글
[gRPC] C# gRPC 서버(Service) 샘플 만들어보기 (0) | 2023.01.09 |
---|---|
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기. (0) | 2023.01.04 |
[gRPC] gRPC 알아보기 (0) | 2023.01.02 |
[DI] 의존성 관리하기 (2) (0) | 2019.12.25 |
[DI] 의존성 관리하기 (1) (0) | 2019.12.17 |
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기.
들어가며
gRPC를 보다보니, gRPC는 기본적으로 프로토콜 버퍼를 함께 사용합니다. 저도 아직 실무에서 사용해본적은 없지만 이번기회에 개념을 같이 정리해보았습니다.
개요
프로토콜 버퍼는 구조화된 데이터를 직렬화하기 위한 구글에서 개발하고, 공개한 언어 중립적, 플랫폼 중립적,
확장 가능한 구조입니다. (Serialized Data Structure)
다양한 언어를 지원하며, XML 보다 작고, 빠르고, 간단하며, 일반적으로 RESTFul API 사용시 JSON을 많이 사용하며, 이와 유사한 직렬화 데이터 구조입니다.
JSON, XML의 경우 일반 사람들이 육안으로 데이터 확인이 가능하다면, 프로토콜 버퍼의 구조는 16진수로 인코딩 하기때문에, 직접적인 확인은 불가능하다는 단점이 있지만, 데이터 구조화 를 한번 정의한 다음, 특수 생성 소스 코드를 사용하여 다양한 데이터 스트림과 다양한 언어를 사용하여 구조화된 데이터를 쉽게 쓰고 읽을 수 있습니다.
사용범위
- 프로토콜 버퍼는 언어 중립적, 플랫폼 중립적, 확장 가능한 방식으로 구조화된 레코드와 같은 유형 데이터를 직렬화해야 하는 모든 상황에 이상적입니다.
- 통신 프로토콜 정의(gRPC와 함께) 및 데이터 저장에 가장 자주 사용됩니다.
장점
- 컴팩트한 데이터 스토리지
- 빠른 구문 분석
- 많은 프로그래밍 언어에서 사용 가능
- 자동 생성 클래스를 통한 최적화된 기능
단점
- 16진수 형태의 byte 로 변환되기 때문에, 직관적으로 데이터를 읽기 어렵습니다.
- 타 언어의 경우 .proto 확장자 파일이 있어야, protobuf 로 쓰여진 데이터를 읽을 수 있습니다.
언어 간 호환성
지원되는 모든 프로그래밍 언어로 작성된 코드로 동일한 메시지를 읽을 수 있습니다. 한 플랫폼의 Java 프로그램이 한 소프트웨어 시스템에서 데이터를 캡처하고 .proto정의에 따라 직렬화한 다음 다른 플랫폼에서 실행되는 별도의 Python 응용 프로그램에서 직렬화된 데이터에서 특정 값을 추출하도록 할 수 있습니다.
- C++, C#, Java, Kotlin, Objective-C, PHP, Python, Ruby
적합하지 않은 경우
- 프로토콜 버퍼가 직렬화가 되면, 동일한 데이터여도 다양한 이진 직렬화를 가질 수 있습니다. 이진데이터를 비교해야 할 경우 다른 솔루션을 고려해야 합니다.
- 메시지는 압축되지 않습니다. gzip으로는 압축할 수 있지만, 특수 목적 압축 알고리즘에는 적합하지 않습니다.
- 비 객체지향 언어에서 잘 지원되지 않습니다. (언어 호환 확인 필요)
- 공식적인 표준이 아닙니다. 표준을 따라야 하는 프로젝트에서는 부적합합니다.
참조
https://developers.google.com/protocol-buffers/docs/overview
END
'Programming' 카테고리의 다른 글
[gRPC] C# gRPC 서버(Service) 샘플 만들어보기 (0) | 2023.01.09 |
---|---|
[gRPC] C# 프로토콜 버퍼 사용하기 (0) | 2023.01.06 |
[gRPC] gRPC 알아보기 (0) | 2023.01.02 |
[DI] 의존성 관리하기 (2) (0) | 2019.12.25 |
[DI] 의존성 관리하기 (1) (0) | 2019.12.17 |
[gRPC] gRPC 알아보기
들어가며
Web 서버로 구성된 프로젝트에서, 서버 클라이언트간에 데이터를 전송하는 방식으로 RESTFul API 에 Json 데이터 포멧을 이용해서 많이 사용합니다. 분명한 장/단점이 있는데요. 클라이언트와의 데이터 포멧은 데이터 파악의 용이한 점으로 인해서 Json 포멧을 많이 사용하고 있습니다.
서버끼리의 통신에도 Json 방식으로 데이터 포멧을 유지하고 있었는데요.
데이터 구조를 노출하지 않고 (물론 다양한 방법이 존재하지만요.), 서버간의 통신을 gRPC를 이용해서 좀더 빠른 통신을 구성해보는 것이 어떨까 해서 gRPC 에 대해 알아보기로 했습니다.
gRPC 개요
gRPC는 google Remote Procedure Call 의 약자로서, 이름에서 보이듯 구글에서 만든, 모든 환경에서 실행할수 있는 최신 오픈 소스 고성능의 원격 프로시져 호출 프래임워크 입니다.
RPC는 프로세스 간에 통신을 하기 위한 프레임워크인데, 이를 구글에서 HTTP/2 기반으로 구현한 것이 gRPC 입니다.
로드밸런싱, 추척, 상태확인 및 인증을 위한 플러그형 지원을 통해 데이터 센터 안밖에서 서비스를 효율적으로 연결할 수 있습니다. 또한 디바이스, 모바일 앱, 브라우저의 백앤드 서비스에 연결하기 위한 분산 컴퓨팅의 라스트 마일에도 적용할 수 있습니다.
gRPC에서 클라이언트는 로컬 개체인것처럼 다른 서버 응용프로그램의 메서드를 직접 호출할 수 있으므로, 분산 응용 프로그램 및 서비스를 더 쉽게 만들 수 있습니다.
서버측에서 인터페이스를 구현하고 gRPC 서버를 실행하여, 클라이언트 호출을 처리합니다.
프로토콜 버퍼
기본적으로 gRPC는 프로토콜 버퍼를 사용합니다.
일반적으로 proto2(현재 기본 프로토콜 버퍼 버전)를 사용할 수 있지만 proto3를 gRPC와 함께 사용하면 gRPC 지원 언어의 전체 범위를 사용할 수 있고 proto2 클라이언트와 통신하는 호환성 문제를 피할 수 있으므로 gRPC와 함께 사용하는 것이 좋습니다.
proto3 서버와 그 반대의 경우도 마찬가지입니다.
주요 사용 시나리오
- 마이크로 서비스 스타일의 설계에서 폴리글랏 서비스의 효율적인 연결
- 모바일 장비, 브라우저 클라이언트의 백엔드 서비스에 연결
- 효율적인 클라이언트 라이브러리 생성
핵심 특징
- 11개 언어로 된 관용적 클라이언트 라이브러리
- 간단한 서비스 정의 프레임워크로 매우 효율적입니다.
- http/2 기반 전송을 통한 양방향 스트리밍
- 플러그인 형 인증, 추적, 로드밸런싱 및 상태확인
지원 언어
Language | OS | Compilers / SDK |
C/C++ | Linux, Mac | GCC 6.3+, Clang 6+ |
C/C++ | Windows 10+ | Visual Studio 2017+ |
C# | Linux, Mac | .NET Core, Mono 4+ |
C# | Windows 10+ | .NET Core, NET 4.5+ |
Dart | Windows, Linux, Mac | Dart 2.12+ |
Go | Windows, Linux, Mac | Go 1.13+ |
Java | Windows, Linux, Mac | Java 8+ (KitKat+ for Android) |
Kotlin | Windows, Linux, Mac | Kotlin 1.3+ |
Node.js | Windows, Linux, Mac | Node v8+ |
Objective-C | macOS 10.10+, iOS 9.0+ | Xcode 12+ |
PHP | Linux, Mac | PHP 7.0+ |
Python | Windows, Linux, Mac | Python 3.5+ |
Ruby | Windows, Linux, Mac | Ruby 2.3+ |
참조
https://narup.tistory.com/119?category=925261
https://grpc.io/docs/what-is-grpc/introduction/
END
'Programming' 카테고리의 다른 글
[gRPC] C# 프로토콜 버퍼 사용하기 (0) | 2023.01.06 |
---|---|
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기. (0) | 2023.01.04 |
[DI] 의존성 관리하기 (2) (0) | 2019.12.25 |
[DI] 의존성 관리하기 (1) (0) | 2019.12.17 |
[DI] 의존성과 계층화 (0) | 2019.12.16 |
[DI] 의존성 관리하기 (2)
객체 생성에 대한 제안
인터페이스를 기초로 한 코딩
가장 중요한 변경은 SecurityService 클래스의 실제 구현을 인터페이스 뒤로 숨기는 것이다.
이렇게 하면, AccountController 클래스가 SecurityService 클래스의 실제 구현체가 아닌 인터페이스에만 의존하게 만들 수 있다.
▶ 인터페이스 추출
public interface ISecurityService
{
void ChangeUsersPassword(string userID, string newPassword);
}
// ...
public class SecurityService : ISecurityService
{
public void ChangeUsersPassword(string userID, string newPassword)
{
// ...
}
}
public class AccountController
{
private readonly ISecurityService securityService;
public AccountController()
{
this.securityService = new SecurityService();
}
[HttpPost]
public void ChangePassword(string userID, string newPassword)
{
securityService.ChangeUserPassword(userID, newPassword);
}
}
위 예제는 아직도 생성자에서 SecurityServer 클래스를 호출함으로 완벽하지 않다. 아직도 의존성은 존재한다.
이 두 클래스를 완전히 분리하기 위해서는 더 리팩토링이 필요하다.
이 시점에서 의존성 주입 ( DI, Dependency Injection )을 알아보자.
의존성 주입 기법 활용하기
AccountController 클래스는 SecurityService 클래스의 인스턴스를 직접 생성하는 대신, 다른 클래스에게 ISecurityService 인터페이스를 구현한 객체를 제공해줄것을 요구한다.
그리고 null 값을 전달하는 경우를 방지하기 위한 조건 역시 포함한다.
이렇게 함으로써, ChangePassword 메서드에서 securityService 필드를 참조할 때, 이 필드가 항상 유효한 인스턴스를 참조하도록 보장할 수 있어, 그 어디에서도 null 값 검사를 수행할 필요가 없게 된다.
public interface ISecurityService
{
void ChangeUsersPassword(string userID, string newPassword);
}
public class SecurityService : ISecurityService
{
private readonly IUserRepository userRepository;
public SecurityService(IUserRepository userRepository)
{
if(userRepository == null) throw new ArgumentNullException("userRepository");
this.userRepository = userRepository;
}
public void ChangeUsersPassword(string userID, string newPassword)
{
var user = userRepository.GetByID(userID);
user.ChangePassword(newPassword);
}
}
public class AccountController
{
private readonly ISecurityService securityService;
public AccountController(ISecurityService securityService)
{
if(securityService == null) throw new ArgumentNullException("securityService");
this.securityService = securityService;
}
[HttpPost]
public void ChangePassword(string userID, string newPassword)
{
securityService.ChangeUserPassword(userID, newPassword);
}
}
AccountController 클래스가 유효한 ISecurityService 인터페이스의 인스턴스를 전달하기를 강요하는 것 처럼 SecurityService 클래스 또한 유효한 IUserRepository 인터페이스의 인스턴스를 전달할 것을 강요한다.
마찬가지로, UserRepository 클래스에 대한 의존성 역시 IUserRepository 인터페이스에 의해 완전히 제거되었다.
해당 글은 C#으로 배우는 적응형 코드 서적을 토대로 개념을 정리중인 글입니다.
'Programming' 카테고리의 다른 글
[gRPC] 프로토콜 버퍼 (Protocol Buffers) 알아보기. (0) | 2023.01.04 |
---|---|
[gRPC] gRPC 알아보기 (0) | 2023.01.02 |
[DI] 의존성 관리하기 (1) (0) | 2019.12.17 |
[DI] 의존성과 계층화 (0) | 2019.12.16 |
[C#] interface 와 abstract class 의 차이 (0) | 2019.12.12 |
[DI] 의존성 관리하기 (1)
의존성 관리하기
의존성으로 인해 발생할 수 있는 문제점들은 실제로 문제점들이 드러난 후에는 되돌릴기가 어려울 수도 있다.
시작단계에서 의존성을 관리하고, 지속적으로 관심을 두어 문제가 발생하지 않도록 해야 한다.
구현과 인터페이스의 비교
인터페이스를 기반으로 하는 프로그래밍에 익숙하지 않은 개발자는 인터페이스의 이면을 이해하는데 어려움을 격는다.
컴파일 시에는 인터페이스의 클라이언트는 해당 인터페이스에 대한 어떤 구현체가 사용되고 있는지에 대해 전혀 알 필요가 없다.
new 키워드의 코드 스멜
인터페이스는 어떤 일을 수행할 수 있는지를 서술하며, 클래스는 어떻게 특정 작업을 수행할 것인지를 서술 한다.
실제 구현에 대한 상세 내용은 오직 클래스만이 알고 있다.
즉, 인터페이스는 해당 작업이 어떻게 수행되는지에 대해서는 철저히 무관심 해야 한다.
A는 B이다 라는 관점에서 볼때 new 키워드를 사용한다는 것은 코드스멜(code smell)로 취급할 수 있다.
코드스멜 : 어떤 코드가 잠재적으로 (potentially) 문제가 있을 수 있음을 표현하는 단어이다.
new 키워드를 이용해 객체의 인스턴스를 직접 생성하는 코드 스멜 예
public class AccountController
{
private readonly SecurityService securityService;
public AccountController()
{
this.securityService = new SecurityService();
}
[HttpPost]
public void ChangePassword(string userID, string newPassword)
{
var userRepository = new UserRepository();
var user = userRepository.GetByID(userID);
this.securityService.ChangeUserPassword(user, newPassword);
}
}
이 코드에는 여러 문제가 내포되어 있으며, 두번의 new 키워드 사용은 다음 문제점들의 원인이 된다.
- AccountController 클래스는 SecurityService 클래스와 UserRepository 클래스의 구현에 영원히 의존적이 된다.
- SecurityService 클래스와 UserRepository 클래스가 가지고 있는 의존성은 AccountController 클래스의 잠재적 의존성이 된다.
- AccountController는 단위 테스트를 하기가 어려워졌다. 평범한 방법으로는 의존하고 있는 두 클래스의 모의 객체(mocking object)를 만들 수 없기 때문이다.
- SecurityService.ChangeUserPassword 메서드는 클라이언트가 User 객체를 로드할 수 밖에 없도록 만든다.
SecurityService 클래스의 구현을 변경할 수 있는 방법은 두가지있다.
1. AccountController가 새롭게 구현한 클래스를 사용 할 수 있도록 수정하는 것.
2. 기존의 SecurityService 클래스에 새로운 기능을 추가하는 것.
테스트 가능성의 부재
테스트 가능성은 매우 중요한 항목이며, 이를 위해서는 코드 자체가 특정한 방식을 통해 디자인되어야 한다.
AccountController 클래스와 SecurityService 클래스는 쉽게 테스트하기가 어렵다.
그 이유는 두 클래스가 가지고 있는 의존성을 실제로는 아무런 기능도 수행하지 않는 모의객체로 교체할 수가 없기 때문이다.
보다 부적절한 결합
AccountController.ChangePassword 메서드는 UserRepository클래스의 인스턴스를 생성하여 User 객체의 인스턴스를 조회한다. 그렇게 해야 하는 이유는 SecurityService.ChangeUserPassword 매서드가 User 객체를 필요로 하기 때문이다.
즉, User 객체의 인스턴스를 얻지 못하면 메서드를 호출 할 수 없다. 이런 형태는 잘못된 메서드 인터페이스 디자인으로 꼽힌다.
[HttpPost]
public void ChangePassword(string userID, string newPassword)
{
this.securityService.ChangeUserPassword(user, newPassword);
}
...
public void ChangeUserPassword(string userID, string newPassword)
{
var userRepository = new UserRepository();
var user = userRepository.GetByID(userID);
user.ChangePassword(newPassword);
}
이렇게 하면 AccountController 클래스는 개선되지만,
ChangeUserPassword 메서드는 UserRepository 객체의 인스턴스를 직접 생성하는 문제점을 여전히 가지고 있다.
해당 글은 C#으로 배우는 적응형 코드 서적을 토대로 개념을 정리중인 글입니다.
'Programming' 카테고리의 다른 글
[gRPC] gRPC 알아보기 (0) | 2023.01.02 |
---|---|
[DI] 의존성 관리하기 (2) (0) | 2019.12.25 |
[DI] 의존성과 계층화 (0) | 2019.12.16 |
[C#] interface 와 abstract class 의 차이 (0) | 2019.12.12 |
[C#] 인터페이스 (interface)에 대한 정리 (0) | 2019.12.06 |