.NET Quickstart — 설치부터 첫 요청까지¶
이 장을 읽고 나면
패키지를 설치하고, 두 process가 서로 호출하는 최소 project를 실행할 수 있다.
저장소의 framework/languages/dotnet/quickstart/
프로젝트다. 아래 코드 블록은 사이트를 빌드할 때 그 파일에서 읽는다. location store 없이
process 둘이 서로의 endpoint를 직접 지정해 request/reply 한 번을 주고받는다.
0. 예제 저장소 clone¶
이 장의 project는 zlink-dotnet-examples 저장소의 quickstart/이며, 기능 가이드가 읽는
program인 tutorial/과 samples/도 같은 저장소에 있다.
git clone https://github.com/zlink-systems/zlink-dotnet-examples.git
cd zlink-dotnet-examples/quickstart
main은 최신 릴리스에 그 뒤의 수정을 더한 것이고, 패키지 버전은 그 릴리스에 맞춰져 있다.
이전 릴리스는 tag vA.B.C로 받는다(git checkout vA.B.C). 이슈와 PR은 zlink-systems/zlink로
보낸다.
1. 설치¶
- .NET SDK 8.0 이상 (
net8.0) - nuget.org 접근
NuGet에서 받는다. 서버 하나를 만들 때 필요한 최소 조합은 다음과 같다.
# 계약과 runtime. core 메시징 엔진(Zlink)을 전이 의존으로 포함한다
dotnet add package Zlink.Framework
# DI·hosted service 등록(AddZLinkFramework)
dotnet add package Zlink.Framework.AspNetCore
Zlink(binding)는 적지 않는다. Zlink.Framework가 의존 버전을 선언한다.
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- 계약과 runtime. Zlink(binding)는 여기서 고정하지 않고
Zlink.Framework의 전이 의존에 맡긴다. -->
<PackageVersion Include="Zlink.Framework" Version="0.30.0" />
<!-- DI·hosted service 등록(AddZLinkFramework). -->
<PackageVersion Include="Zlink.Framework.AspNetCore" Version="0.30.0" />
</ItemGroup>
</Project>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
필요할 때 추가하는 package는 다음과 같다.
| package | 언제 추가하나 |
|---|---|
Zlink.Framework.Locations.Redis |
Redis location store로 자동 연결을 사용할 때(Location) |
Zlink.Framework.Codecs.Protobuf · .MessagePack |
기본 JSON codec 대신 사용할 때(Handler와 메시지 처리) |
Zlink.Stream.Connector |
외부 client(게임 client·모바일)를 만들 때(STREAM) |
Zlink.HttpClient |
서버에서 HTTP를 호출할 때(HTTP Client 가이드) |
라이선스는 계층마다 다르다 — core·binding은
MPL-2.0, framework는 FSL-1.1-ALv2, Zlink.HttpClient는 Apache-2.0이다. 서비스를 만들어
파는 데 드는 비용은 없다.
2. 공유 계약¶
namespace QuickStart.Shared;
// Request/reply contract shared by the server and client processes.
public sealed record Hello(string Name);
public sealed record Greeting(string Text);
3. 처리하는 쪽¶
IZLinkRequestHandler<,>를 구현한 handler는 Channel(...).Server().AddRequestHandler<...>()에
직접 등록한다. 어느 channel에 노출할지가 이 등록으로 정해진다.
using QuickStart.Shared;
using Zlink.Framework.AspNetCore;
using Zlink.Framework.Contracts.Handlers;
var builder = WebApplication.CreateBuilder(args);
// This process does not expose HTTP; the port only needs to differ from the
// client process's port when both run on the same host.
builder.WebHost.UseUrls("http://127.0.0.1:5081");
builder.Services.AddZLinkFramework(options =>
{
// Names the mesh and opens this process's endpoint for peers to connect to.
var mesh = options.AddRouteMesh("services").Listen("tcp://127.0.0.1:7101");
// This process handles the "greeting" channel. An IZLinkRequestHandler<,>
// class registers on the channel builder; AddHandlersFromAssemblyOf wires
// only attribute-declared handlers.
mesh.Channel("greeting").Server().AddRequestHandler<HelloHandler, Hello, Greeting>();
});
var app = builder.Build();
await app.RunAsync();
// Handles one request on the "greeting" channel.
public sealed class HelloHandler : IZLinkRequestHandler<Hello, Greeting>
{
public ValueTask<Greeting> HandleAsync(
Hello request,
IZLinkMessageContext context,
CancellationToken cancellationToken
) => ValueTask.FromResult(new Greeting($"hello, {request.Name}"));
}
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<RootNamespace>QuickStart.Server</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Zlink.Framework" />
<PackageReference Include="Zlink.Framework.AspNetCore" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Shared/Shared.csproj" />
</ItemGroup>
</Project>
4. 호출하는 쪽¶
두 process를 같은 host에서 실행하므로 HTTP 포트를 각각 지정한다. 지정하지 않으면 Kestrel 기본 포트가 충돌한다.
using QuickStart.Shared;
using Zlink.Framework.AspNetCore;
using Zlink.Framework.Contracts.Channels;
var builder = WebApplication.CreateBuilder(args);
// http://127.0.0.1:5080/hello/{name} is this process's own HTTP surface.
builder.WebHost.UseUrls("http://127.0.0.1:5080");
builder.Services.AddZLinkFramework(options =>
{
// This process also needs its own endpoint.
var mesh = options.AddRouteMesh("services").Listen("tcp://127.0.0.1:7102");
// This side only calls; it does not handle "greeting".
mesh.Channel("greeting").Client();
// Manual connection — the server's endpoint is given directly.
mesh.PeerConnections.Connect("tcp://127.0.0.1:7101");
});
var app = builder.Build();
app.MapGet(
"/hello/{name}",
async (string name, IZLinkRouteClient route, CancellationToken cancellationToken) =>
{
// The target is a single ChannelName; which node handles it is not specified.
var reply = await route
.RequestToChannel("greeting", new Hello(name))
.Async<Greeting>(cancellationToken);
return Results.Ok(reply.Text);
}
);
await app.RunAsync();
5. 실행¶
cd zlink-dotnet-examples/quickstart
dotnet build
# 터미널 두 개. server를 먼저 실행한다.
dotnet run --project Server/Server.csproj
dotnet run --project Client/Client.csproj
curl http://127.0.0.1:5080/hello/world
응답은 "hello, world", 상태 코드 200이다.
6. 첫 실행이 안 될 때 확인할 항목¶
| 증상 | 확인할 항목 |
|---|---|
| package를 찾지 못한다 | §1의 package 이름을 그대로 적었는지 확인한다. binding 버전은 따로 고정하지 않는다 |
| startup이 실패한다 | 두 process의 mesh 이름이 같은지, listen endpoint가 다른 process와 겹치지 않는지 확인한다 |
| 호출이 대상 없음으로 끝난다 | 받는 쪽이 그 channel 이름을 server 역할로 등록했는지, 두 process가 peer로 연결됐는지 확인한다 |
| 응답이 오지 않는다 | 보낸 쪽이 request를 썼는지 확인한다. send는 응답을 받지 않는다 |
7. 옮겨 갈 것¶
| 파일 | 내용 |
|---|---|
Directory.Packages.props |
PackageVersion 두 항목. binding은 전이 의존으로 둔다 |
Shared/Contracts.cs |
record 기반 메시지 정의 |
Server/Program.cs |
AddRouteMesh → Listen → Channel(...).Server().AddRequestHandler<...>() |
Client/Program.cs |
Channel(...).Client(), PeerConnections.Connect(...), RequestToChannel(...).Async<T>() |
8. 다음으로 읽을 것¶
이 두 process는 endpoint를 서로 직접 적어 연결한다. 서버를 늘리거나 다른 주소로 다시 시작해도 호출 코드를 그대로 두려면 자동 연결이 필요하고, 그것은 Location이 다룬다.
- 개념을 먼저 확인할 때 — 핵심 개념
- 이름으로 호출하는 경로 — Channel 메시징
- id로 호출하는 상태 객체 — Spot · Actor
- 완결된 업무 흐름을 볼 때 — 샘플 고르기