SignalR API 디자인 고려 사항

작성자: Andrew Stanton-Nurse

이 문서에서는 SignalR 기반 API를 빌드하기 위한 지침을 제공합니다.

사용자 지정 개체 매개 변수를 사용하여 이전 버전과의 호환성 보장

클라이언트 또는 서버에서 SignalR 허브 메서드에 매개 변수를 추가하는 것은 주요 변경 사항입니다. 즉, 이전 클라이언트/서버에서 적절한 수의 매개 변수를 사용하지 않고 메서드를 호출하려고 하면 오류가 발생합니다. 그러나 사용자 지정 개체 매개 변수에 속성을 추가하는 것은 주요 변경 사항이 아닙니다. 이를 사용하여 클라이언트 또는 서버의 변경 내용에 대해 복원력이 있는 호환 API를 디자인할 수 있습니다.

예를 들어 다음과 같은 서버 쪽 API를 살펴보겠습니다.

public int GetTotalLength(string param1)
{
    return param1.Length;
}

JavaScript 클라이언트는 다음과 같이 invoke를 사용하여 이 메서드를 호출합니다.

connection.invoke("GetTotalLength", "value1");

나중에 서버 메서드에 두 번째 매개 변수를 추가하는 경우 이전 클라이언트는 이 매개 변수 값을 제공하지 않습니다. 예시:

public int GetTotalLength(string param1, string param2)
{
    return param1.Length + param2.Length;
}

이전 클라이언트에서 이 메서드를 호출하려고 하면 다음과 같은 오류가 발생합니다.

Microsoft.AspNetCore.SignalR.HubException: Failed to invoke 'GetTotalLength' due to an error on the server.

서버에 다음과 같은 로그 메시지가 표시됩니다.

System.IO.InvalidDataException: Invocation provides 1 argument(s) but target expects 2.

이전 클라이언트는 매개 변수를 하나만 보냈지만 최신 서버 API에는 두 개의 매개 변수가 필요합니다. 사용자 지정 개체를 매개 변수로 사용하면 더 많은 유연성이 제공됩니다. 사용자 지정 개체를 사용하도록 원래 API를 다시 디자인해 보겠습니다.

public class TotalLengthRequest
{
    public string Param1 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    return req.Param1.Length;
}

이제 클라이언트는 개체를 사용하여 메서드를 호출합니다.

connection.invoke("GetTotalLength", { param1: "value1" });

매개 변수를 추가하는 대신 TotalLengthRequest 개체에 속성을 추가합니다.

public class TotalLengthRequest
{
    public string Param1 { get; set; }
    public string Param2 { get; set; }
}

public int GetTotalLength(TotalLengthRequest req)
{
    var length = req.Param1.Length;
    if (req.Param2 != null)
    {
        length += req.Param2.Length;
    }
    return length;
}

이전 클라이언트에서 단일 매개 변수를 보낼 때 추가 Param2 속성은 그대로 null을 유지합니다. Param2에서 null을 확인하고 기본값을 적용하여 이전 클라이언트에서 보낸 메시지를 검색할 수 있습니다. 새 클라이언트는 두 매개 변수를 모두 보낼 수 있습니다.

connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });

클라이언트에 정의된 메서드에 대해 동일한 기술이 작동합니다. 서버 쪽에서 사용자 지정 개체를 보낼 수 있습니다.

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Message = message
    });
}

클라이언트 쪽에서 다음 매개 변수를 사용하는 대신 Message 속성에 액세스합니다.

connection.on("ReceiveMessage", (req) => {
    appendMessageToChatWindow(req.message);
});

나중에 메시지 보낸 사람을 페이로드에 추가하기로 결정한 경우에는 개체에 속성을 추가합니다.

public async Task Broadcast(string message)
{
    await Clients.All.SendAsync("ReceiveMessage", new
    {
        Sender = Context.User.Identity.Name,
        Message = message
    });
}

이전 클라이언트에는 Sender 값이 필요하지 않으므로 무시됩니다. 새 클라이언트는 새 속성을 읽도록 업데이트하여 이를 수락할 수 있습니다.

connection.on("ReceiveMessage", (req) => {
    let message = req.message;
    if (req.sender) {
        message = req.sender + ": " + message;
    }
    appendMessageToChatWindow(message);
});

이 경우 새 클라이언트도 Sender 값을 제공하지 않는 이전 서버를 허용합니다. 이전 서버는 Sender 값을 제공하지 않으므로 클라이언트는 액세스하기 전에 존재하는지 여부를 확인합니다.

추가 리소스