Skip to content
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

Composing multiple interfaces for one API. #49

Closed
6D65 opened this issue Jul 2, 2014 · 14 comments
Closed

Composing multiple interfaces for one API. #49

6D65 opened this issue Jul 2, 2014 · 14 comments
Labels

Comments

@6D65
Copy link

6D65 commented Jul 2, 2014

Hi,

Firstly, sorry if this is a obvious question, but is it possible to have one big interface/api defined in multiple pieces?

Maybe a folder structure like this? Where each interface is in its own folder inside a class.

/Acme
  AcmeApi.cs (IAcmeApi interface)
  /photos
    /IPhotosApi (acme.com/api/photos)
  /videos
   /IVideosApi (acme.com/api/videos)

then using just the IAcmeApi interface

var acmeApi = RestService.For<IAcmeApi>("https://api.acme.com");

var stuff = await acmeApi.DoStuff("banana");
// or maybe
var stuff = await acmeApi.Videos.DoStuff("banana");

I hope it makes sense.
Thank you.

@6D65 6D65 changed the title Combine multiple interfaces. Composing multiple interfaces for one API. Jul 2, 2014
@anaisbetts
Copy link
Member

This isn't possible right now, the best you can do currently is use partial classes (Partial interfaces? Is that a thing?) The idea of nesting objects is kind of interesting though

@6D65
Copy link
Author

6D65 commented Jul 3, 2014

Yeah. I'm new to this library, and i'm experimenting with it. Maybe one singe big interface is a better solution, having all the endpoints in one place, it's easier to navigate and understand. I'll play around more. Thanks.

@dahlbyk
Copy link
Contributor

dahlbyk commented Jul 3, 2014

It seems reasonable for interface-returning properties on API interfaces to return Refit proxies for the returned interface.

@nekresh
Copy link
Contributor

nekresh commented Dec 6, 2014

I've begun adding support for it (see here).

Currently, a refit interface can be composed of other refit interfaces like this:

public interface IServicePart
{
   [Get("/")]
   Task<List<string>> GetUsers();

   [Get("/{user}")]
   Task<string> GetUser(string user);
}

public interface IService
{
   [Compose("/users")]
   IServicePart Users { get; }

The attribute is mandatory here, unless we have all the compile time types (requiring another part of Roslyn).
Of course, we can compose multiple interface, even generic interfaces.

What do you think of it ?

@bennor
Copy link
Contributor

bennor commented Dec 7, 2014

I'm not opposed to it but I'm not sure we need the compose attribute to generate the stubs - as long as all of the interfaces you're composing are in the same project, you should be able to pick up any wrapper interfaces with a second pass:

  1. Run through interfaces and detect all Refit interfaces (as we currently do)
  2. Run through interfaces again and look for any that have properties which are in the first list

We could run into problems without an attribute though, because it would mean having to pass the relative paths in when calling RestService.For() which could get a little clunky. I think I prefer the attribute.

@paulcbetts?

I think it looks good so far. What else is there still to do on it (other than tests)? I think anything that we're building into the stubs that will throw an exception should raise a warning at build time too, so you'll want to do that as soon as we get #79 merged.

@anaisbetts
Copy link
Member

Am I understanding the semantics right here?

public interface IServicePart
{
   // Actually makes a request to /users
   [Get("/")]
   Task<List<string>> GetUsers();

   // Actually makes a request to /users/{user}
   [Get("/{user}")]
   Task<string> GetUser(string user);
}

public interface IService
{
   [Compose("/users")]
   IServicePart Users { get; }
}

How is this better (this is not a rhetorical question, it very may well be!) than:

public interface IService
{
   IServicePart Users { get; }
}

public class Service 
{
    IServicePart Users {
        get { return RestService.For<IServicePart>("https://myservice.com/users"); }
    }
}

@nekresh
Copy link
Contributor

nekresh commented Dec 10, 2014

It still allows you to have refit method inside your interface and compose it.

This will work:

public interface IServicePart
{
   // Actually makes a request to /users
   [Get("/")]
   Task<List<string>> GetUsers();

   // Actually makes a request to /users/{user}
   [Get("/{user}")]
   Task<string> GetUser(string user);
}

public interface IService
{
   [Compose("/users")]
   IServicePart Users { get; }

   [Get("/")]
   Task<List<string>> Get();
}

Maybe it is too much and interface composition shouldn't be mixed with refit service definition.

@bennor
Copy link
Contributor

bennor commented Dec 10, 2014

How is this better (this is not a rhetorical question, it very may well be!)...

I think it's potentially worse actually - you lose the control over the HttpClient that you get with a direct call to RestService.For<IServicePart>(someClient).

If we go ahead with it, we will need to make sure we allow for a single HttpClient instance to be shared by all nested calls to RestService.For(), which could get complicated.

@bennor bennor closed this as completed Dec 10, 2014
@bennor bennor reopened this Dec 10, 2014
@bennor
Copy link
Contributor

bennor commented Dec 10, 2014

Sorry, accidentally clicked the button.

@anaisbetts
Copy link
Member

I think you could just do this better with Extension Methods:

public interface IService
{
   [Get("/")]
   Task<List<string>> Get();
}

public static class UsersExtension
{
    public static IServicePart Users(this IService This) {
        return RestService.For<IServicePart>("https://myservice.com/users");
    } 
}

@nekresh
Copy link
Contributor

nekresh commented Dec 11, 2014

Looks good.
You lose the garantee that both services will use the same base address but it will probably be easier to use.

Currently, I don't have [Header(...)] inheritance in sub-service, so you have to specify it everywhere.
If it isn't needed, no need to support the whole sub-service generation.

@anaisbetts
Copy link
Member

Let's close this out then, unless someone comes up with a super cool use-case

@nekresh
Copy link
Contributor

nekresh commented Dec 11, 2014

Maybe we could update the Readme to include your solution ?

@anaisbetts
Copy link
Member

@nekresh Can you submit a PR to do that?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

5 participants