-
Notifications
You must be signed in to change notification settings - Fork 86
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
[RED-1966] Adds support to an iterator and more CBP endpoints #322
Conversation
@mikerogers123 I would appreciate some love on this one, too 🙏 |
|
||
public async Task NextPage() | ||
{ | ||
Console.WriteLine("fetching the next page... "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any chance can we remove this Console.WriteLine? Or at least use the dotnet ILogger
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, removed. this was not supposed to be there :)
|
||
namespace ZendeskApi.Client.Resources | ||
{ | ||
public class PaginatedResource : AbstractBaseResource<PaginatedResource>, IPaginatedResource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just trying to wrap my head around this resource. I can see that this is only used in one place: in the private async Task ExecuteRequest(string requestUrl)
method of the iterator class.
To my mind, these resource classes (and by convention anything implementing AbstractBaseResource
) represent resources in Zendesk. This does not represent a particular resource and appears to be a helper method of sorts. I can see why you have gone this route - I assume to keep all HTTP requests performed by resources. This does carry weight, but s jeopardises Liskov Substitution Principle. What makes this most prevalent is the fact that the base class enforces the docsResource
parameter in the base to be present, but this breaks that contract.
In terms of an alternatives I can think off off the bat...
-
I think you just want access to that
ApiClient
object... and might be achieved via Dependency Injection rather than inheritance. I think you can just injectIZendeskApiClient
into a class which creates the iterator. -
One option is to use a factory to create
CursorPaginatorIterator
. In this factory you could inject the ApiClient dependency needed to make the request. -
At the very least we could make the
PaginatedResource
internally visible because it is only intended for internal use. If we go down that route then a better option might be to have a internal class (not a resource) within the project which exposes the Client object you need. That way it can be injected internally within the package to be used for adhoc HTTP requests like this. -
Not sure what it might look like but a bigger change would be to make the response type from our CBP endpoints to be
CursorPaginatedIterator<T>
instead ofICursorPagination<T>
. That way we remove the need for consumer to translate from one to the other and just give them the useful thing back
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @mikerogers123 🙏
what do you think of my last commit just pushed? I completely removed the the PaginatedResource
. I agree it didn't make much sense. I switched dependancies on the CursorPaginationIterator
class to now receive an ApiClient rather than a ZendeskClient. This way I can directly call the method I want. It slightly changes how we do the setup now
var client = serviceProvider.GetRequiredService<IZendeskClient>();
var apiClient = serviceProvider.GetRequiredService<IZendeskApiClient>();
var ticketCursorResponse = await client.Tickets.GetAllAsync(new CursorPager { Size = 2 });
var iterator = new CursorPaginatedIterator<Ticket>(ticketCursorResponse, apiClient);
Hence the changes in the Factory to accommodate the tests. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fbvilela This is definitely an improvement. However, I think there is still one issue. The way a consumer typically would use this client is by injecting an instance of IZendeskClient
. But with this suggestion/change they would also need IZendeskApiClient
in order to use CursorPaginatedIterator
. I can only speak from the context of JET's usage but I don't think this is ideal. Ideally, all paginated responses would "out-of-the-box" support this iteration. I'm wondering if one of the following may be better:
- Make all cursor endpoints return a
CursorPaginatedIterator
instance. This conversion from the existingCursorPaginationResponse<T>
toCursorPaginatedIterator<T>
could perhaps be done in theAbstractBaseResource
to minimise the blast radius - if this change was too large, then we could have a
ICursorPaginatedIteratorFactory
interface which consumers could use to to the conversion. That way the container can control the instantiation ofIZendeskApiClient
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @mikerogers123,
I think your first suggestion would be cool but also a big and breaking change. Since we don't know exactly how many customers are using this SDK, we would much prefer to not change existing behaviour.
about your second idea, I might be missing something here... would this factory be responsible for creating a IZendeskApiClient
to pass to the CursorPaginatedIterator
?
another idea, could we perhaps make a new method in the ZendeskClient.cs
in order to execute the request?
This class only has Resources so I'm not sure we would be breaking any sort of design pattern here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if there's anything else we could work on. it would be great to finish off this work :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @fbvilela apologies for the delay here.
would this factory be responsible for creating a IZendeskApiClient to pass to the CursorPaginatedIterator ?
Yes! So the issue I have with the current approach is that consumers now need to know about IZendeskApiClient
, purely for the sake if instantiating this CursorPaginatedIterator
. This exposes details/inner workings of the package they dont really care about. They just want to create an instance of CursorPaginatedIterator
, which is where Dependency Injection and Factory Pattern would come into it
@fbvilela overall I'm a big fan of using the iterator suggested here. I think we just need to iron out the precise implementation as I have outlined in the above comments 👍 |
Hi @mikerogers123 :) I think I finally got it... or at least I'm close. thanks for your patience here as I learn my way through C#. is this in line with what you had in mind? |
@@ -7,20 +7,26 @@ namespace ZendeskApi.Client.IntegrationTests.Factories | |||
{ | |||
public class ZendeskClientFactory | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure having a static member of this factory is a good idea. Being static suggests that this IZendeskClient field member can be adopt a value at compile-time. When in fact, this value gets mutated by calling GetApiClient. If possible I think this class should remain as-is, and just expose a non-static factory method, inline with the Factory pattern. I think this is possible, since we are discussing having the IZendeskApiClient abstracted inside the iterator/factory in another convo, which negates the need for this new method to be exposed on this class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fbvilela is it possible to revert the change to this file? I don't think we use theis GetApiClient method anywhere other than tests
using Microsoft.Extensions.DependencyInjection; | ||
using ZendeskApi.Client.Responses; | ||
|
||
namespace ZendeskApi.Client.Models |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can probably move the iterator/factory to a more suitable folder/namespace. Perhaps something like ZendeskApi.Client.Pagination
or something?
|
||
public class CursorPaginatedIteratorFactory : ICursorPaginatedIteratorFactory | ||
{ | ||
private static IServiceProvider serviceProvider; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we are getting closer to the end result here. There are still a few points I would like to make...
This serviceProvider
field member should not be static, it should be readonly
. We should also not be injecting the IServiceProvder
into a class. This endangers running into the Service Locator anti-pattern. I won't go into too much detail here but in short the IoC container is the thing responsible for choosing the correct dependency - in general you don't want a consuming class acting as a locator. The usual pattern for injecting dependencies in dotnet is closer to the following
namespace ZendeskApi.Client.Pagination
{
public interface ICursorPaginatedIteratorFactory
{
CursorPaginatedIterator<T> Create<T>(ICursorPagination<T> response);
}
public class CursorPaginatedIteratorFactory : ICursorPaginatedIteratorFactory
{
// readonly field as only set in constructor
private readonly IZendeskApiClient zendeskApiClient;
public CursorPaginatedIteratorFactory(IZendeskApiClient _zendeskApiClient)
{
// inject IZendeskApiClient after registering in the container
zendeskApiClient = _zendeskApiClient;
}
public CursorPaginatedIterator<T> Create<T>(ICursorPagination<T> response)
{
return new CursorPaginatedIterator<T>(response, zendeskApiClient);
}
}
}
This means a consumer looks like the following:
public class YourConsumerClass
{
private readonly CursorPaginatedIteratorFactory iteratorFactory;
public YourConsumerClass(ICursorPaginatedIteratorFactory _iteratorFactory)
{
iteratorFactory = _iteratorFactory;
}
public void YourMethod(ICursorPagination<YourType> response)
{
// Use the factory to get the iterator
CursorPaginatedIterator<YourType> iterator = iteratorFactory.Create(response);
// Rest of your logic using the iterator...
}
}
To summarise, from where you are we may consider
- alter
ICursorPaginatedIteratorFactory
:- inject
IZendeskApiClient
directly, instead of theIServiceProvider
- make the
IZendeskApiClient
field member private, readonly - not static
- inject
- Register your new interface/implementation in the IoC container. We have helper methods which we recommend using here.
Aside
I want to reiterate that, in an ideal world, consumers would be able to iterate over the response from paginated endpoints without injecting another dependency on top of IZendeskClient
. One approach we already discussed was to change the return type of all CBP endpoints to be the iterator itself. We discounted this because of the blast-radius of the change. It's worth noting that this is probably not that dangerous. As you have explored recently these existing CBP methods don't work properly so people are probably not using it meaningfully. So now is probably the best time to make that change. I do understand your hesitancy though and it may be one for us owners to pick up if you wanted to.
Another approach that works around this is to use the adapter pattern. At the moment the CBP endpoints return ICursorPagination<T>
. An adapter class would adapt the adapt the method signature of the CBP methods to return CursorPaginatedIterator<T>
. It would work in a similar way to the IteratorFactory you have but the usage is different. You would expose the adapted resources on the IZendeskClient interface so users would consume like so:
client.AdaptedTicketAudit.GetAllAsync()
where the AdaptedTicketAudit
is named for clarity but essentially the underlying resource would implement a different interface to ITicketAuditResource
Not suggesting this adapter as an approach because the amount of code changes required here is massive and I prefer the formerly suggested. But it just came to my mind so wanted to share in case it sparked anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @mikerogers123, I believe I made the (small) changes you requested 😅
to be very clear, I'm a Ruby developer who picked up this work of updating the C# SDK in order to unblock Zendesk from rolling out the OBP deprecation. How is it looking after this last commit? The usage I wrote in the PR description remains unchanged and I'm registering the Factory here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for that @fbvilela the changes look good overall. Have added a few more minor comments to sort, then we should be close to closing this one
@@ -7,20 +7,26 @@ namespace ZendeskApi.Client.IntegrationTests.Factories | |||
{ | |||
public class ZendeskClientFactory | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fbvilela is it possible to revert the change to this file? I don't think we use theis GetApiClient method anywhere other than tests
@@ -17,4 +17,10 @@ | |||
<PackageReference Include="System.Reflection.Extensions" Version="4.3.0" /> | |||
</ItemGroup> | |||
|
|||
<ItemGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this?
using ZendeskApi.Client; | ||
using ZendeskApi.Client.Responses; | ||
|
||
public class CursorPaginatedIterator<T> : IEnumerable<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add a namespace to this class? And then move it into the Pagination folder?
thanks @mikerogers123 🙏 I made the changes you requested apart from removing the |
@fbvilela awesome stuff. I am happy to see this merged! Only last thing is to bump the version number 😆 after that we can approve/merge. Annoyingly though, I am leaving Just Eat Takeaway in the next day or so. This means that I cannot merge / release the changes. You will have to get one of the other admins in on this just to nurse it over the line. If I don't hear again then it has been a pleasure working with you on the recent changes. Great to see this library get some TLC ❤️ |
Hi Team, I have bumped the version. This is good to be merged 🙏 |
If I could have contributor's permissions I would bother you way less :) Let me know if there's anything else we can do to get this merged. Thanks |
@albertodebortoli 👋 seems like your main dev for this repo has left the company. Can anyone else help push this trough? |
@brainmurphy maybe something you can help with? |
🙏 |
Thank you so much team....Looking forward to moving forward on this. |
BTW, we do have an impending deadline on our switchover to CBP from OBP. This PR facilitates that switchover for our mutual customers and provides a seamless transition. We are targeting end of January for this to begin, so any help in making this transition easier, and more expedient for our mutual customers is appreciated. Please feel free to reach out. cc @brainmurphy @albertodebortoli |
@Ud0o @UlianaShym @corneliuskopp fyi ❤️ |
Hi @fbvilela, apologies for the radio silence since Mikes departure. I'll look at getting this merged in and a new package published once the build is passing, if you can update the code accordingly. Thanks |
@Ud0o Thanks so much, we'll look into it ASAP, we'll probably have it sorted very early next week (Monday :) ❣️ |
thanks @Ud0o , Can you approve the CI build so we can see if we get a green build? 🙏 I'm thinking the failure could have been because methods were made obsolete in |
Hi @fbvilela @cryptomail, Thats been merged and released now, small transient error with one of the new integration tests, but a rerun seemed to have solved it. You can find version 7.0.7 here Thanks for the changes, and if we are slow to reply again please tag anyone from Mikes comment here. Our team created and maintains this repository, but since its inception our goals have moved into a different problem space not involving Zendesk, hence the long reply. I'll kick off a conversation internally about getting either or both of you setup with contributor/write roles. Until next time! 👍 |
@Ud0o thanks so much, we really appreciate it! We look forward to helping this project forward in the future. |
Description
Adds support to the iterator pattern
There are now a few ways to loop through pages using this SDK
Adds support to more CBP endpoints
A few
Resources
already have CBP capabilities but were still missing the implementation on this SDK.Could be individually reviewed by the commits that mention
CBP
Updates the README/Documentation where the changes were made