-
Notifications
You must be signed in to change notification settings - Fork 58
Feature Request: add interfaces to all public classes #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@HunteRoi thanks! Could you clarify what you’re wanting to do? I’m not sure I understand. |
Hey there! That's a quick response. What I am willing to do is basically create a service that uses Supabase as a dependency. However I usually try to not register dependencies as concrete implementations but rather tend to use interfaces for it. So far, I could write an AuthService that looks like the following: public class AuthService : IAuthService
{
private readonly Client _gotrueClient;
public AuthService(Client gotrueClient)
{
_gotrueClient = gotrueClient ?? throw new ArgumentNullException(nameof(gotrueClient));
}
public Task<bool> SignIn(string email)
{
return _gotrueClient.SignIn(email);
}
} where I want to create something like: public class AuthService : IAuthService
{
private readonly IClient _gotrueClient;
public AuthService(IClient gotrueClient)
{
_gotrueClient = gotrueClient ?? throw new ArgumentNullException(nameof(gotrueClient));
}
public Task<bool> SignIn(string email)
{
return _gotrueClient.SignIn(email);
}
} (notice the EDIT: of course, this changes imply to create interfaces for everything that is public (a quick way to generate interfaces on Visual Studio is to select the class then EDIT 2: this could also be the ground work of a Supabase.DependencyInjection package that would contain code for the default .NET ioc container, and for other well known ones such as AutoFac, NInject, and so on... |
@HunteRoi I'm not at all opposed to implementing this - but I have to admit my ignorance, the |
The dependency injection (DI) software design pattern is often used in C# with the .NET framework. If you want to know more, I recommend looking into these sources:
For examples of it being used, I would consider looking into repositories that use the .NET framework or directly in the framework's code. Do you need some time to think about it or should I look into making a PR ? |
As an addition and because I now see that I did not mention it before: I need interfaces in order to unit test my components and this cannot be done with concrete implementations (because it wouldn't be unit tests anymore but more like integration tests). [TestFixture]
internal class AuthServiceTests
{
- private Mock<Client> GotrueClientMock { get; set; }
- // ^ wrong because I can't mock an implementation
+ private Mock<IClient> GotrueClientMock { get; set; }
+ // ^ right because I can mock the behaviour of the public methods and getters/setters
private AuthService AuthService { get; set; }
// skipping initialization for the sake of simplicity
[Test]
public void SignIn_Should_ThrowArgumentNullException_When_EmailIsNull()
{
ArgumentNullException expected = new();
string email = null;
this.GotrueClientMock.Setup(client => client.SignIn(email)).Throws(expected).Verifiable();
// ^ calling Setup wouldn't throw if the mock was using an abstraction class instead of implementation's
var actual = Assert.Throws<ArgumentNullException>(() => this.AuthService.SignIn(email));
Assert.AreSame(expected, actual);
}
} |
@HunteRoi thanks for being so thorough with your responses! This looks like a great thing to implement for the main library and its children. It does look like it will significantly change the way the API currently functions - I'm assuming that implementing DI will restructure the entire project, correct? As in, we won't be able to maintain compatibility with the current API after the changes? If you have the time to do a PR on one of the client libraries that would be much appreciated. The easiest to implement would likely be functions-csharp but if you're up to go all out, gotrue-csharp would be great. Since this is a little out of my wheelhouse, having something to reference (that is done correctly!) would be most helpful. |
@acupofjose Like you said, the changes on each repository would represent a huge breaking change. To summarize everything so far: I have created this issue for a need of myself (ie. add an abstraction layer to inject dependencies thus allowing me to unit test my components). However, it seems like there is a lot to do and unfortunately I do not have enough time to do it on my own... For example, I have taken a look at functions-csharp and its // IClient.cs
namespace Supabase.Functions;
public interface IClient
{
Task<HttpConent> RawInvoke(string url, string token = null, InvokeFunctionOptions options = null);
Task<string> Invoke(string url, string token = null, InvokeFunctionOptions options = null);
Task<T> Invoke<T>(string url, string token = null, InvokeFunctionOptions options = null);
} // Client.cs
namespace Supabase.Functions;
public class Client : IClient
{
private readonly IHttpClientFactory _httpClientFactory;
public Client (IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
}
public async Task<HttpConent> RawInvoke(string url, string token = null, InvokeFunctionOptions options = null)
=> (await HandleRequest(url, token, options)).Content;
public Task<string> Invoke(string url, string token = null, InvokeFunctionOptions options = null)
{
return Invoke<string>(url, token, options);
}
public async Task<T> Invoke<T>(string url, string token = null, InvokeFunctionOptions options = null)
{
var response = await HandleRequest(url, token, options);
var content = await response.Content.ReadAsStringAsync();
return typeof(T) == content.GetType() ? content : JsonConvert.DeserializeObject<T>(content);
}
private async Task<HttpResponse> HandleRequest(string url, string token = null, InvokeFunctionOptions options = null)
{
if (url == null) throw new ArgumentNullException(nameof(url));
if (string.IsNullOrWhiteSpace(url)) throw new ArgumentException("Cannot be empty or whitespaces", nameof(url));
options ??= new InvokeFunctionOptions();
if (!string.IsNullOrEmpty(token))
{
options.Headers["Authorization"] = $"Bearer {token}";
}
options.Headers["X-Client-Info"] = Util.GetAssemblyVersion();
var client = _httpClientFactory.CreateClient();
var builder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(builder.Query);
builder.Query = query.ToString();
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, builder.Uri);
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(options.Body), Encoding.UTF8, "applicatin/json");
if (options.Headers != null)
{
foreach(var headerKeyPerValue in options.Headers)
{
requestMessage.Headers.TryAddWithoutValidation(headerKeyPerValue.key, headerKeyPerValue.Value);
}
}
var response = await client.SendAsync(requestMessage);
if (!response.IsSuccessStatusCode || response.Headers.Contains("X-Relay-Error"))
{
var content = await response.Content.ReadAsStringAsync();
var errorResponse = new ErrorResponse { Content = content, Message = content };
throw new RequestException(response, errorResponse);
}
return response;
}
} We could argue that it still depends on |
Thanks for taking the time to look at this @HunteRoi! I've been poking around with As to the static functions - this was in response to #7 where there was a request for accessing stateless functionality. My (incomplete, working) proposal:
For example: public class Client : IGotrueClient
{
internal IGotrueApi api;
// new, optional constructor
public Client(string url, string apiKey, IGotrueApi gotrueApi = null)
{
//...
if (api != null)
{
api = gotrueApi;
}
else
{
api = new Api(url, apiKey);
}
}
}
Unfortunately, this means that the libraries would be sort of straddling both worlds, which is not ideal. I'm curious as to your thoughts on that approach though? |
@HunteRoi I've created a PR on the gotrue-csharp repo for you to review. The docs are currently placeholders as the plugin that was suggested requires (AFAIK) the pro version to transfer comments from the methods to the interfaces. |
I will take a look at it! Regarding the pro version, you are probably right. If so, I am sorry for the mislead. Anyway thank you for your time! Give me some time to review the changes. I have a tough week, I am not sure I can free up a lot of time but I will try my best! |
No rush, good luck out there! |
My 2 cents: I always avoid doing that if type isn't meant to be replaced as it makes everything more complex. The worst is trying to find concrete interface implementation between multiple classes. Imo avoid adding interfaces if they are not needed. |
Hello everyone!
I would firstly like to thank you for the work you have done to create this package.
As the title might suggest, I am here to ask something of you... Would it be possible to have your opinions on the abstraction layer that is missing in Supabase-csharp and its dependencies? I would have expected to be able to use the dependency injection with this package (in order to create my own wrapper around it and not naively inject a concrete class around in my projects).
Nonetheless thank you very much for the ground work on what proves to be the next great addition to the Open Source Community!
The text was updated successfully, but these errors were encountered: