Skip to content

Architecture

sonvister edited this page Mar 16, 2018 · 5 revisions

The purpose of this document is to describe the core separation of JSON producers and consumers and how they form the layered architecture of this library as well as to explain the is-a and has-a relationships of the interfaces and implementations of this library.

This is not intended to be a 'how-to' guide. Currently, the sample applications still offer the best demonstrations and example use of this library. For those looking for the simplest application-level development, consider using the web socket clients and caches or the composite IBinanceWebSocketClientManager.

Diagram (partial)

IJsonProducer

This fundamental interface defines a JSON message producer. This can be anything that produces JSON messages (in Binance format or otherwise) such as a Web Socket, HTTP client, or database API (for back-testing/simulation).

public interface IJsonProducer
{
    event EventHandler<JsonMessageEventArgs> Message;
}

NOTE: Application-level development will typically not use this 'low-level' event for anything other than logging or troubleshooting. Also, since some JSON data can contain sensitive information, it is no longer logged automatically (but, can still be logged at the application-level using this event).

JsonMessageEventArgs

The JSON message event arguments include the message data as a JSON object or array and the message Subject, which defines the context of the message. For instance, the IBinanceHttpClient and IWebSocketClient implementations use their respective URI.AbsoluteUri as the message subject, while IJsonStream implementations use the stream name.

public sealed class JsonMessageEventArgs
{
    public string Subject { get; }

    public string Json { get; }
}

NOTE: When using combined streams, the IWebSocketClient message is the raw JSON before being separated into stream name and data.

IBinanceHttpClient

This is the IJsonProducer interface to the Binance REST API using a single HttpClient that returns 'raw' JSON. The specific methods are implemented as extensions in BinanceHttpClientExtensions. New capabilities can be added as extensions of IBinanceHttpClient in a similar fashion (at the application level) utilizing the shared, singleton, BinanceHttpClient.

Deserialization

The top-level IBinanceAPI interface deserializes the JSON data from IBinanceHttpClient into convenient domain value-objects. All of the deserializers used in BinanceApi are available in the Binance.Serialization namespace for application-level deserialization of 'raw' Binance JSON data (for instance, if stored for back-testing/simulation).

IJsonStream

This defines an abstract, streaming, JSON producer. Streaming is controlled by calling the StreamAsync method and by providing a required CancellationToken to abort the streaming operation. The stream is 'closed' when the Task has completed (and IsStreaming is false). There is no Error event because exceptions can and should be caught with try/catch (when using async) or with the AggregateException via the returned Task.

NOTE: Users of this library are expected to be familiar with TAP.

NOTE: The IJsonStream implementations handle subscribe/unsubscribe of streams whether streaming is active or not. However (depending on the implementation), if changes are made while streaming, the flow of data may be interrupted (this is the case with BinanceWebSocketStream since the web socket client must be aborted and another connection established with a new URI).

public interface IJsonStream : IJsonProducer
{
    bool IsStreaming { get; }

    Task StreamAsync(CancellationToken token);
}

IBufferedJsonProducer

This IJsonProducer decorator buffers data from another JSON producer.

public interface IBufferedJsonProducer<TProducer> : IJsonProducer
    where TProducer : IJsonProducer
{
    TProducer JsonProducer { get; }
}

IWebSocketStream

This interface defines a specific implementation of IJsonStream that uses a 'low-level' IWebSocketClient to provide JSON data, which can be accessed via the WebSocket property. The extending IBinanceWebSocketStream interface provides an additional IsCombined property.

NOTE: An IWebSocketStream is not an IWebSocketClient. The IWebSocketStream is a specific form of IJsonStream that uses an IWebSocketClient. The IWebSocketClient.StreamAsync() method combines connect with receive operations and returns a Task, in-part, controlled by the IJsonStream. The Open event is raised after a connection is established. The Close event is raised just before the Task completes. Stream controllers need only manage the StreamAsync() returned Task and not concern themselves with the implementation details of the IJsonStream (i.e. the Open and Close events).

public interface IWebSocketClient : IJsonProvider
{
    event EventHandler<EventArgs> Open;

    event EventHandler<EventArgs> Close;

    Task StreamAsync(Uri uri, CancellationToken token);
}
public interface IWebSocketStream : IJsonStream
{
    IWebSocketClient WebSocket { get; }
}
public interface IBinanceWebSocketStream : IWebSocketStream
{
    bool IsCombined { get; }
}

IJsonPublisher

This defines a JSON publisher. New message notification is sent to subscribed observers or Message event handlers, where the message subject is the stream name. When subscribing to a stream, the subscriber is optional (if using an event handler instead). The PublishedStreams property lists the unique stream names that are currently subscribed.

NOTE: The IJsonStream implementations handle subscribe/unsubscribe of streams whether streaming is active or not. However (depending on the implementation), if changes are made while streaming, the flow of data may be interrupted (this is the case with BinanceWebSocketStream since the web socket client must be aborted and another connection established with a new URI).

public interface IJsonPublisher : IJsonProducer
{
    IEnumerable<string> PublishedStreams { get; }

    IJsonPublisher Subscribe(IJsonSubscriber subscriber, params string[] streamNames);

    IJsonPublisher Unsubscribe(IJsonSubscriber subscriber, params string[] streamNames);

IJsonSubscriber

This interface provides a means of identifying classes as observers of a IJsonStream as passed to the subscribe/unsubscribe methods as well as providing the ability to handle message events. Any class can handle the IJsonProducer.Message event, but that handler will receive all 'raw' JSON messages (depending on context of the event).

public interface IJsonSubscriber
{
    IEnumerable<string> SubscribedStreams { get; }

    IJsonSubscriber Unsubscribe();

    void HandleMessage(string stream, string json);
}

IJsonClient

These JSON stream subscribers have the capability of deserializing JSON into higher-level objects and providing notification via subscribed callbacks and/or non-specific events. Each Binance implementation is responsible for interpreting JSON from one or more of the official web socket endpoints.

public interface IJsonClient : IJsonSubscriber
{ }

NOTE: As JSON subscribers, the clients, can be used to process JSON from any compatible IJsonProducer implementation.

IAggregateTradeClient

Processes JSON data from Aggregate Trade streams. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface IAggregateTradeClient : IJsonClient
{
    event EventHandler<AggregateTradeEventArgs> AggregateTrade;

    IAggregateTradeClient Subscribe(string symbol, Action<AggregateTradeEventArgs> callback);

    IAggregateTradeClient Unsubscribe(string symbol, Action<AggregateTradeEventArgs> callback);
}

ITradeClient

Processes JSON data from Trade streams. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface ITradeClient : IJsonClient
{
    event EventHandler<TradeEventArgs> Trade;

    ITradeClient Subscribe(string symbol, Action<TradeEventArgs> callback);

    ITradeClient Unsubscribe(string symbol, Action<TradeEventArgs> callback);
}

ICandlestickClient

Processes JSON data from Candlestick streams. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface ICandlestickClient : IJsonClient
{
    event EventHandler<CandlestickEventArgs> Candlestick;

    ICandlestickClient Subscribe(string symbol, CandlestickInterval interval, Action<CandlestickEventArgs> callback);

    ICandlestickClient Unsubscribe(string symbol, CandlestickInterval interval, Action<CandlestickEventArgs> callback);
}

ISymbolStatisticsClient

Processes JSON data from Symbol Ticker streams and All Market Tickers stream. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface ISymbolStatisticsClient : IJsonClient
{
    event EventHandler<SymbolStatisticsEventArgs> StatisticsUpdate;

    ISymbolStatisticsClient Subscribe(Action<SymbolStatisticsEventArgs> callback, params string[] symbols);

    ISymbolStatisticsClient Unsubscribe(Action<SymbolStatisticsEventArgs> callback, params string[] symbols);
}

IDepthClient

Processes JSON data from Partial Book streams and Diff. Depth streams. The partial depth stream is used if a limit is specified. Currently, the only valid values for the partial book depth stream are: 5, 10, or 20. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface IDepthClient : IJsonClient
{
    event EventHandler<DepthUpdateEventArgs> DepthUpdate;

    IDepthClient Subscribe(string symbol, int limit, Action<DepthUpdateEventArgs> callback);

    IDepthClient Unsubscribe(string symbol, int limit, Action<DepthUpdateEventArgs> callback);
}

IUserDataClient

Processes JSON data from User Data streams. When subscribing or unsubscribing, the callback is optional (extension methods are available).

public interface IUserDataClient : IJsonClient
{
    event EventHandler<AccountUpdateEventArgs> AccountUpdate;

    event EventHandler<OrderUpdateEventArgs> OrderUpdate;

    event EventHandler<AccountTradeUpdateEventArgs> TradeUpdate;`

    IUserDataClient Subscribe<TEventArgs>(string listenKey, IBinanceApiUser user, Action<TEventArgs> callback)
        where TEventArgs : UserDataEventArgs;

    IUserDataClient Unsubscribe<TEventArgs>(string listenKey, Action<TEventArgs> callback)
        where TEventArgs : UserDataEventArgs;

    void HandleListenKeyChange(string oldListenKey, string newListenKey);

IJsonClientCache

These classes add an in-memory, thread-safe, cache extending the IJsonClient interface. After an event is received from the IJsonClient the cache is updated and a translated Update event is fired (and callbacks are notified).

NOTE: IJsonClientCache has an IJsonClient and is an IJsonClient. The Client can be set and, if previously subscribed, the cache will automatically unsubscribe (its context) from the existing client and subscribe (the cache context) to the new client.

NOTE: These classes do not include a JSON stream or stream controller.

public interface IJsonClientCache<TClient, TEventArgs> : IJsonClient
    where TClient : IJsonClient
    where TEventArgs : CacheEventArgs
{
    event EventHandler<TEventArgs> Update;

    TClient Client { get; set; }
}
public abstract class CacheEventArgs : EventArgs
{ }

IAccountInfoCache

An IJsonClientCache that keeps an in-memory copy of the latest account info updated by an IUserDataClient.

public interface IAccountInfoCache
    : IJsonClientCache<IUserDataWebSocketManager, AccountInfoCacheEventArgs>
{
    AccountInfo AccountInfo { get; }

    void Subscribe(string listenKey, IBinanceApiUser user, Action<AccountInfoCacheEventArgs> callback);
}

IAggregateTradeCache

An IJsonClientCache that loads the latest aggregate trades using IBinanceApi and keeps an in-memory list of the latest aggregate trades updated by an IAggregateTradeClient. When a new aggregate trade is received the oldest is removed from the in-memory list.

public interface IAggregateTradeCache
  : IJsonClientCache<IAggregateTradeClient, AggregateTradeCacheEventArgs>
{
    event EventHandler<EventArgs> OutOfSync;

    IEnumerable<AggregateTrade> Trades { get; }

    void Subscribe(string symbol, int limit, Action<AggregateTradeCacheEventArgs> callback);

ICandlestickCache

An IJsonClientCache that loads the latest candlesticks using IBinanceApi and keeps an in-memory list of the latest candlesticks updated by an ICandlestickClient. When the latest candlestick is closed and a new candlestick is received the oldest candlestick is removed from the in-memory list.

public interface ICandlestickCache
    : IJsonClientCache<ICandlestickClient, CandlestickCacheEventArgs>
{
    IEnumerable<Candlestick> Candlesticks { get; }

    void Subscribe(
        string symbol, CandlestickInterval interval, int limit,
        Action<CandlestickCacheEventArgs> callback);
}

IOrderBookCache

An IJsonClientCache that loads the latest order book using IBinanceApi and keeps an in-memory OrderBook updated by an IDepthClient.

public interface IOrderBookCache
    : IJsonClientCache<IDepthClient, OrderBookCacheEventArgs>
{
        event EventHandler<EventArgs> OutOfSync;

        OrderBook OrderBook { get; }

        void Subscribe(string symbol, int limit, Action<OrderBookCacheEventArgs> callback);
}

ISymbolStatisticsCache

An IJsonClientCache that loads the latest 24-hour statistics using IBinanceApi for a symbol or symbols and keeps an in-memory array updated by an ISymbolStatisticsClient.

public interface ISymbolStatisticsCache
    : IJsonClientCache<ISymbolStatisticsClient, SymbolStatisticsCacheEventArgs>
{
    IEnumerable<SymbolStatistics> Statistics { get; }

    SymbolStatistics GetStatistics(string symbol);

    IEnumerable<SymbolStatistics> GetStatistics(params string[] symbols);

    void Subscribe(Action<SymbolStatisticsCacheEventArgs> callback);

    void Subscribe(Action<SymbolStatisticsCacheEventArgs> callback, params string[] symbols);
}

ITradeCache

An IJsonClientCache that loads the latest trades using IBinanceApi and keeps an in-memory list of the latest trades updated by an ITradeClient. When a new trade is received the oldest is removed from the in-memory list.

public interface ITradeCache
    : IJsonClientCache<ITradeClient, TradeCacheEventArgs>
{
    event EventHandler<EventArgs> OutOfSync;

    IEnumerable<Trade> Trades { get; }

    void Subscribe(string symbol, int limit, Action<TradeCacheEventArgs> callback);
}

IJsonPublisherClient

This is the first interface to combine the behavior of a JSON producer and consumer. However, as with IJsonClient, these classes do not provide a stream controller; an external controller is required to call Stream.StreamAsync() and manage the returned Task. The TaskController and RetryTaskController utility classes are available for this purpose, but are not required.

public interface IJsonPublisherClient<out TPublisher> : IJsonClient
    where TPublisher : IJsonPublisher
{
    TPublisher Publisher { get; }
}

IWebSocketPublisherClient

This interface extends IJsonPublisherClient and uses a web socket stream to provide JSON data. Implementations are responsible for translating application-level arguments into a Binance stream name and coordinating the subscribing of that stream name between the JSON stream (producer) and client (consumer).

The specific implementations are used like their JSON client counterparts, but include automatic stream/controller/task management. A watchdog timer is added to abort streaming if data has not been received for a configurable time interval (default 1 hour). If a timeout occurs and streaming is aborted, streaming will be restarted by the IRetryTaskController after a configurable delay. Internal exceptions are accessible from the Error event.

public interface IWebSocketPublisherClient : IJsonPublisherClient<IAutoJsonStreamPublisher<IWebSocketStream>>, IError
{ }

IAggregateTradeWebSocketClient

An IAggregateTradeClient that uses a IBinanceWebSocketStream.

public interface IAggregateTradeWebSocketClient
    : IAggregateTradeClient, IBinanceWebSocketClient
{ }

ITradeWebSocketClient

An ITradeClient that uses a IBinanceWebSocketStream.

public interface ITradeWebSocketClient
    : ITradeClient, IBinanceWebSocketClient
{ }

ICandlestickWebSocketClient

An ICandlestickWebSocketClient that uses a IBinanceWebSocketStream.

public interface ICandlestickWebSocketClient
    : ICandlestickClient, IBinanceWebSocketClient
{ }

ISymbolStatisticsWebSocketClient

An ISymbolStatisticsWebSocketClient that uses a IBinanceWebSocketStream.

public interface ISymbolStatisticsWebSocketClient
    : ISymbolStatisticsClient, IBinanceWebSocketClient
{ }

IDepthWebSocketClient

An IDepthClient that uses a IBinanceWebSocketStream.

public interface IDepthWebSocketClient
    : IDepthClient, IBinanceWebSocketClient
{ }

IUserDataWebSocketClient

An IUserDataClient that uses a IBinanceWebSocketStream.

public interface IUserDataWebSocketClient
     : IUserDataClient, IBinanceWebSocketClient
{ }

IUserDataWebSocketManager

A manager that uses a IBinanceWebSocketStream. This interface does not extend IUserDataClient (future implementations might). The implementation also encapsulates user listen key management with an IUserDataWebSocketStreamControl.

public interface IUserDataWebSocketManager : IControllerManager<IWebSocketStream>
{
    event EventHandler<AccountUpdateEventArgs> AccountUpdate;

    event EventHandler<OrderUpdateEventArgs> OrderUpdate;

    event EventHandler<AccountTradeUpdateEventArgs> TradeUpdate;

    Task SubscribeAsync<TEventArgs>(IBinanceApiUser user, Action<TEventArgs> callback,
        CancellationToken token = default)
            where TEventArgs : UserDataEventArgs;

    Task UnsubscribeAsync<TEventArgs>(IBinanceApiUser user, Action<TEventArgs> callback,
        CancellationToken token = default)
            where TEventArgs : UserDataEventArgs;

    Task UnsubscribeAllAsync(CancellationToken token = default);

IUserDataWebSocketStreamControl

This interface handles user data web socket open, keep-alive, and close API calls and management of user listen keys. When a user is added, a listen key is queried and returned. An internal timer is used to keep-alive each user data stream with a ping every 30 minutes (default). If the ping fails, a new key is queried and listeners are notified via the ListenKeyUpdate event.

public interface IUserDataWebSocketStreamControl : IDisposable
{
    event EventHandler<UserDataListenKeyUpdateEventArgs> ListenKeyUpdate;

    IEnumerable<IBinanceApiUser> Users { get; }

    TimeSpan KeepAliveTimerPeriod { get; set; }

    Task<string> GetStreamNameAsync(IBinanceApiUser user, CancellationToken token = default);

    Task<string> OpenStreamAsync(IBinanceApiUser user, CancellationToken token = default);

    Task CloseStreamAsync(IBinanceApiUser user, CancellationToken token = default);

    Task CloseAllStreamsAsync(CancellationToken token = default);
}
Clone this wiki locally