Browse Source

Add design document for 3.0 interfaces

3.0-old
FiniteReality 5 years ago
parent
commit
b57b913119
1 changed files with 94 additions and 0 deletions
  1. +94
    -0
      docs/design/interfaces.md

+ 94
- 0
docs/design/interfaces.md View File

@@ -0,0 +1,94 @@
# Discord.Net 3.0 Interface Design #

This is mostly just a collection of notes for interface design for 3.0. As
such, don't expect them to be comprehensive or up to date.

## Why no `ITextChannel`, `IGuildChannel`, etc? ##

Usually, it's better to design types in terms of the fields they have, rather
than using a discriminated union like the current `IChannel` interface.

However, the design chosen in 1.0 led to [traits](wiki-traits), which in C# was
*notoriously* hard to work with, debug and expand. Most notably, in
`1.0.0-beta2`, the internal implementation details were exposed in form of
`SocketUserMessage`, `SocketTextChannel` etc. simply to make consumption
easier. The effect of this has been that writing portable, re-usable code is
very hard, and has also caused us to be much slower when implementing updates
from Discord.

To simplify this, the current design has gone back to a centralised `IChannel`
type, a la [D#+'s DiscordChannel](dsharpplus-channel) implementation. However,
we are going to be using [Nullable Reference Types](nrts) to help reduce some
of the inevitable errors which will come around with a discriminated union
approach. Furthermore, this design allows in the future a traits-like system to
be implemented on top of it, once the design flaws have been worked out.

## Future Plans ##

Ideally, [shapes] will become a thing, making the previous design *much* easier
to implement and maintain, on top of the current design. To give a small
example:

```cs
shape SChannel<T>
{
public ulong Id { get; }
}

shape SGuildChannel<T> : SChannel<T>
{
public ulong GuildId { get; }
public int? Position { get; }
public IEnumerable<PermissionOverwrite> PermissionOverwrites { get; }
public string Name { get; }
}

shape STextChannel<T> : SChannel<T>
{
public ulong? LastMessageId { get; }
public DateTimeOffset LastPinTimestamp { get; }
}

shape SGuildTextChannel<T> : SGuildChannel<T>, STextChannel<T>
{
public string? Topic { get; }
public bool IsNsfw { get; }
public int? RateLimit { get; }
}

public ValueTask<bool> DeleteLastMessageAsync<T>(T channel)
where T : STextChannel<T>
{
return channel.LastMessageId switch {
null => new ValueTask<bool>(false),
0 => new ValueTask<bool>(false),
_ => new ValueTask<bool>(DeleteMessageAsync(channel.LastMessageId))
};

static async Task<bool> DeleteMessage(ulong messageId)
{
var message = await GetMessageAsync(messageId);

if (message == null)
return false;

await message?.DeleteAsync();
return true;
}
}
```

Internally, `DeleteLastMessageAsync` will work on any channel, but as long
as that channel exposes the correct APIs, the above method will function as the
user expects.

Additionally, this provides a huge advantage in that the above code is
extremely easy to unit test; any type which fulfils the contract of
`STextChannel<T>` can be used, even if it doesn't implement the `IChannel`
interface.


[wiki-traits]: https://en.wikipedia.org/wiki/Trait_(computer_programming)
[dsharpplus-channel]: https://dsharpplus.emzi0767.com/api/DSharpPlus.Entities.DiscordChannel.html
[nrts]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
[shapes]: https://github.com/dotnet/csharplang/issues/164

Loading…
Cancel
Save