-
Notifications
You must be signed in to change notification settings - Fork 43
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
Proposal to implement async API (Futures) #52
Comments
This changes fundamentally how the whole library works and will break any currently working applications using it. That being said I like the idea because enables the possibility of using APIs that push events alongside APIs that are polling all the time. That is the main argument for me to use this approach. The performance is also a good point, but currently is not too bad, specially considering the polling is around 1-2s in the exchanges we are supporting. For me in this context some mili seconds are not a big deal. I specially agree with the statement:
So go for it! If you need help let me know. |
@ainestal Thanks for your input. Indeed, this is a breaking change (Coinnect 0.6 here we go). For the performance, this is exactly the same as before if the Future contains only 1 request but as the number of requests increase the advantage of async API surge. We can add an example where we compare the time taken to pull the price of the pair BTC-ETC for the 4 exchanges with the sync and async method as shown above. My guess is that async version is ≈300% faster |
Hey, I am currently working on streams @ crypto-bank/orca. In the meantime what do you guys think about using #47 and implement traits for generated code? |
@crackcomm I think it can be very useful to have the code generation |
@crackcomm From what I have seen, orca is a Poloniex websocket API implementation, right? Apart from that, I did not study the OpenAPI issue. My plan for now and the foreseeable future is just to write some Rust code and bring Coinnect to a stable version. |
@hugues31 I am currently working only on Poloniex because I am still studying stream design in rust. @ainestal I would like to see that too but currently for APIs like Poloniex etc. that do not provide OpenAPI descriptors I'd rather write code myself like @hugues31. I will also implement async clients in orca but responses will be hard typed. This data is mostly streamed to Python where it can be trained on by TensorFlow. |
@crackcomm I didn't take a close look at the OpenAPI but the code generation feels very useful for the future. In the case of exchanges that don't implement it is probably easier to create the description of the API than to code it in rust. @hugues31 I agree with the approach of coding it now, since we will get the value straight away. I like that over having to wait until we have the code generation and then we can describe all the API, which will take a long time before is completed and usable. |
Hey @hugues31 who is the adventure with the code going? Let me know if I can help. For now I'm waiting until you are done to then help migrating whatever is left (I guess at least Bitstamp) |
Hello @ainestal ! I'm ready to begin the implementation for one exchange just to see what it will looks like. I suggest to take Poloniex as an example since it may interest more people than the others. |
This is the kind of changes that is way easier to do by pairing, but that's kind of complicated in our case. If you see that is very difficult to make it work with all the constraints we had try removing some of them (errors, recreation of the client, etc), we can re introduce them later. |
You can check the new "async" branch. |
I'll take a look when I can, currently I don't have a lot of spare time. |
@hugues31 I was yesterday taking a look at the code and thinking on how to do it. My main assumption is that "We want to deliver futures to the users of the library". Is this correct? If this is true I will work on the code trying to change the least possible parts in order to make it work. |
@ainestal Yes it should be the point. I'm not sure if including the tokio_core inside the API is a good idea thought |
@hugues31 I'm trying to implement just the return of Futures from The part that is making me really struggle to implement this are the errors. I don't know how to pass the error chain to the future. So currently I don't know how to implement it. I'm tempted to try to get rid of the error_chain and see if I can make the library work without it, but feels like going a step backwards. What are your views on this? |
@ainestal I don't understand when you say that with the tokio_core in the API we will return a Result. If you look at my example (first post), it works right away. And indeed, errors need a lot of rework.. |
@hugues31 what I mean returning the Result is also happening in the example. You are using So with that in mind I want to implement futures at the exchange trait level, without modifying anything in any deeper level. Treating the functions of the trait as atomic operations that can be converted into futures has the effect we are looking for and keeps the implementation simpler than populating the futures through the whole library. But it still allows us to use the futures later across all the library if we need to. Btw, I'm still struggling with the errors. I'm trying different approaches without much luck so far. |
Any update on this feature? I've actually implemented an async version of the Poloniex API myself (not public yet, though may never be) before finding this repo. I'd be quite happy to try and implement this feature in this crate. The general syntax in my crate is: use poloniex::Client;
use tokio_core::reactor::Core;
let mut core = Core::new().unwrap();
let polo = Client::new(&core.handle()).unwrap();
// Launch Jobs
let tickers = polo.tickers();
let day_volume = polo.day_volume();
// Tickers
let tickers = core.run(tickers).unwrap();
assert!(tickers.contains_key("BTC_ETH"));
// Day volume
let (pairs, totals) = core.run(day_volume).unwrap();
assert!(pairs.contains_key("BTC_ETH"));
assert!(pairs["BTC_ETH"].contains_key("BTC"));
assert!(pairs["BTC_ETH"].contains_key("ETH"));
assert!(totals.contains_key("totalBTC")); so not too dissimilar to what is proposed. Could be fairly easy for me to adapt what I have to make this crate async. One notable difference is who owns the tokio Would you like me to implement this feature in a fork? |
@JP-Ellis Hello and thank you for contributing to Coinnect. Yes in my mind the tokio Core is owned by Coinnect to simplify. What are the disadvantages to have the tokio Core outside ? The coinnect client is already mutable and we need to change a lot of code to make it immutable (= remove features) so maybe the Core could be owned by the lib ? You are free to start implementing this (long awaited!) feature, I will take a look at it regularly to see what we can do |
In my mind the Let me know if my assumptions are incorrect. |
@ainestal Look at my first post : the Core is owned by the struct but returns Futures. The code works. What's wrong with this idea ? Let me know because I really want to implement an async API 😃 (or let someone do it aha) |
@hugues31 I think the main difference I have in mind between having the Core as part of a specific exchange or having the core outside them is the reusability. If the core is outside the library a user would be able to use it for all the exchanges. If I understand correctly that would allow all the interactions with any of the exchanges to be asynchronous and handled in the same manner, from the same Core. I've been trying to implement futures in the outer layer with the ticker, orderbook, etc, without much luck. Maybe just using the newest version of hyper and using futures through our library is easier to implement. It sure looks like the proper approach that should be used. I just didn't want to do that as a first step because it involves changing everything in the library (almost all functions should be working with futures). Feel free to implement it, I think a non-perfect implementation of async is better than no implementation at all. |
@ainestal We can have the Coinnect handles the different exchanges with the Core loop inside it. In fact, I see no point letting the user to handle it (the user can access the Core since it's public). |
My opinion on this issue is that it would be best to use the same Core structure for every exchange: the main module should dispatch it to each exchange API. Running multiple event loops would inefficient and confusing. How does that sound? |
(note that this architecture lets users choose between Futures and Results: they can call a generic API to manipulate futures themselves, or they can use the functions exposed in the main module to let Coinnect run the requests (which could still be built so that they run asynchronously and in parallel) and return Results) |
I didn't think of using 2 constructors, I like this idea a lot. Anyone is working on that ? I may have some time to prototype that. |
I believe See
Futures can be converted to a result by calling |
@crackcomm yes no need to write a wrapper around functions returning |
I agree, since the OpenAPI codegen generates async code, it wouldn't be wise to try to convert the current exchange code to async. We should probably focus on the boilerplate as @crackcomm said. |
Is there still interest in this lib (and this ticket?) I would like to use Coinnect and am interested in helping to update to async/await. |
Hello @rrichardson , |
@hugues31 - I've also created a provisional repo for the generated code for the various coinnect API clients here : In the next week or two, I'll see what I can do about taking the coinnect data model and adapting it to an async version of At present, my primary interest is in collecting and storing market data, specifically detailed OrderFlow data over Websockets, so I will probably be expanding the API a bit. After that, the fun work of creating OpenAPI yamls for the rest of the "supported" exchanges. I guess, for now, if anyone has an API spec to submit, just make a PR to https://github.com/rrichardson/coinnect_codegen and I'll work on making sure the generator works for that spec. |
Also, thinking more about the streaming data aspects of this: In fact, it might make sense to add a Thoughts? |
With the time, I wonder how we can maintain Coinnect and keep it up to date with the various exchanges supported. We can generate most of the code from what your already implement and a OpenAPI specs file.
I really don't know the best approach for long term maintainability. In the end, I think we will end up with a lot of generated code AND a lot of hand writen code |
We can make the codegen produce any code we want, including code that could lend itself well to directly fitting into the ExchangeApi trait. However, since every exchange has different naming conventions and models, custom code would still need to be written (maybe one day code-generated) to fulfill the ExchangeAPI trait directly. I do think that more work could be done to insulate the Coinnect APIs from the exchange APIs. I'm hoping that for things like trading pairs and other specifics, we can just avoid specifying those altogether, and just pass those parameters through (in a non-typesafe manner) to the inner platform-specific API (who may or may not have types for the pairs) I think we'd definitely want to write/copy API yaml specs for every platform/exchange. That's easier than writing code for every exchange.
Funny enough, this is the first I've ever heard of AsyncAPI :) |
There happen to be a couple relevant discussions happening on /r/rust that relate exactly to trait implementations for separate API/endpoints. It might be overkill to go too far into trait-land, but the more implementation we can save in the long run, the better. I just want to leave these links here so I can find them later: https://www.reddit.com/r/rust/comments/lkdw6o/designing_a_new_architecture_for_rspotify_based/ |
Hello,
After some research, I decided to give a try to Futures to come up with a simple async API. The main idea is to keep everything very straightforward for the user (the event loop is created by the API).
The idea originates from here : #9
The source code is :
Cargo.toml :
The code below returns:
What do you think? Since it's a huge rewrite it would be nice to have some feedbacks :)
The text was updated successfully, but these errors were encountered: