Comments (20)
you both present fair points. idk who but someone suggested that we shall use some kind of response object (for idk what reason, possibly as I think of it - to differentiate unexpected exceptions from expected exceptions), and in order to handle it properly, you need to do something like this:
// this is the current implementation we have
account.hasPermissions(...).whenComplete((res, e) -> {
if (!res.isSuccessful()) {
sender.sendMessage(res.getFailureReason().getDescription());
return;
}
if (e != null) {
// do whatever
return;
}
// do whatever with res.getResult() here
});
however, if we ditch Response
and FailureReason
and create a RuntimeException without a stacktrace, it would look more like this:
account.hasPermissions(...).whenComplete((triStateResult, e) -> {
if (e != null) {
if (e instanceof TreasuryException) { // name under debate, this is for the example
sender.sendMessage(e.getMessage());
return;
}
// do something else with the other exceptions
return;
}
// do whatever with triStateResult here
});
so what i propose is to create a RuntimeException that doesn't have a stacktrace that shall be used instead of Response and FailureReason.
from treasury.
I thought part of the point of swapping to CF over a subscriber was to swap to using CF exception handling instead of baking in our own. This seems more like a question of "is the Response class useful or is it a redundant wrapper when we could be using CF exception handling?"
In my mind, unexpected exceptions should not be included in our intended pipeline. They indicate a problem with the code, so the code should be fixed instead of the exception being handled.
from treasury.
Well response does serve a role because sending a failure reason downstream is much more lighter than an exception. Also that this failure reason can be localized and directly sent to the player. As I said, expected exceptions when working with a database should fallback to a failure response, other exceptions are afaik already caught by CFs.
from treasury.
Otherwise, it may be a good idea to make FailureReasons accept a map of locales to descriptions .
no just add a Locale argument to getDescription
however you make an exception it is still heavy. just look at how much exploits bungeecord has because of it's usage of CancelSendSignal inside the packet handlers. that's how you'll understand how heavy they are.
from treasury.
however you make an exception it is still heavy. just look at how much exploits bungeecord has because of it's usage of CancelSendSignal inside the packet handlers. that's how you'll understand how heavy they are.
I have already posted benchmarks of lightweight "informative" exception handling in previous discussions: #37 (comment)
On modern Java an exception without a trace for information purposes performs very marginally slower than non-exceptional code. It's really not a concern. I get that we still support Java 8, but given that its market share is slipping even more, I don't know that increasing the complexity of error handling when using the API is worth is worth it. The CF already includes all of the error handling, so it's not like we're gaining a performance boost by not throwing any exceptions.
That said, we aren't using CFs because they have error handling built in. We're using them because they clearly signal to users that the results of the call are not necessarily available immediately and the code should not be run synchronously. In that case, perhaps handling errors using the CF instead of our constructs is bad practice? On the user side not handling errors via CF is much easier to remember. Any uncaught exceptions are then a code issue with either the CF provider or the inputs by the user, not potentially informative results.
I just want to be cautious with our approach to this because this is an area where we'll cause everyone a lot of headache if we don't have a firm stance on correct practice.
from treasury.
I think the current stuff as-is is already good because it's a clear signal about whether the request failed or didn't.
from treasury.
Apologies, I thought I had replied here. That seems good to me as well, yeah. Flow all the way through, handle expected failure errors once when handling the expected result. Clean and simple.
from treasury.
That would work. Throwable does have #getLocalizedMessage, so theoretically it is already possible to do to some degree with Transactions, but expanding the capacity and standardizing would be good.
from treasury.
I thought part of the point of swapping to CF over a subscriber was to swap to using CF exception handling instead of baking in our own. This seems more like a question of "is the Response class useful or is it a redundant wrapper when we could be using CF exception handling?"
What do you think, @MrIvanPlays? Since we've removed the standard list of failure reasons (a decision which I still agree with), I'm unsure if Response serves any use anymore.
from treasury.
As we are working with CFs, I am not at all phased by whichever minute overhead exceptions will bring.
Localization is a great point, though note that failure reasons are not localized through the API itself. If we did go down the Exceptions path, we could always make a LocalizedException class which would allow for per-locale descriptions on what happened. Otherwise, it may be a good idea to make FailureReasons accept a map of locales to descriptions .
from treasury.
Otherwise, it may be a good idea to make FailureReasons accept a map of locales to descriptions .
no just add a Locale argument to getDescription
however you make an exception it is still heavy. just look at how much exploits bungeecord has because of it's usage of CancelSendSignal inside the packet handlers. that's how you'll understand how heavy they are.
exceptions in packets are a horrible horrible idea. in workloads which are already heavy, they would contribute next to nothing.
from treasury.
I do like how responses make it easy for implementations to report human-readable issues to users, e.g., account can't afford transaction.
However, I don't like how it is creating an entire extra layer that everyone has to operate with the API.
What would prevent us from just using Exceptions - but - make a special Exception class (which extends RuntimeException) that is intended to be thrown for generic failures, specifically those which are used for FailureReason
?
Implementations would then be encouraged to deal with such an exception differently (use instanceof
to check it), i.e., print the user friendly message to the user, rather than say an 'error occurred, inform an admin'.
This means we no longer have to deal with two separate error handling systems, just one now. Cleaner code, easier usage, less confusion.
On @Jikoo's concern about people not remembering to handle errors in CFs - In our documentation, we can make it very clear that error handling must be done with the CFs. If people are not able to use CFs, then that is their problem, it's part of the Java STL, it's widely documented.
from treasury.
What would prevent us from just using Exceptions - but - make a special Exception class (which extends RuntimeException) that is intended to be thrown for generic failures, specifically those which are used for
FailureReason
?Implementations would then be encouraged to deal with such an exception differently (use
instanceof
to check it), i.e., print the user friendly message to the user, rather than say an 'error occurred, inform an admin'.This means we no longer have to deal with two separate error handling systems, just one now. Cleaner code, easier usage, less confusion.
In my mind this was the original idea for using CFs. A lot of the arguments for and against this are basically rehashes of the discussion we already had about ease of use for beginner programmers.
After #161, I was under the impression that 2.0 was going to be focused on providing an API for advanced users, supplying more documentation and examples to help beginners instead of catering to them. Responses don't really hit that mark for me.
If we are saying "the Subscriber model was good for helping new users but had potential needless overhead" then I agree that the Response model is good. The only benefit is that advanced users will not be wrapping what was (possibly, depending on implementation) internally a CF in another CF.
Here's a Response adaptation of #161's examples:
Response adaptation
public void setBalance(
CommandSender sender, UUID targetPlayerId, BigDecimal amount, Currency currency
) {
EconomyTransactionInitiator<?> initiator;
if (sender instanceof Player) {
initiator = EconomyTransactionInitiator.createInitiator(EconomyTransactionInitiator.Type.PLAYER,
((Player) sender).getUniqueId()
);
} else {
initiator = EconomyTransactionInitiator.SERVER;
}
economy.hasAccount(AccountData.forPlayerAccount(targetPlayerId))
.thenCompose(response -> {
if (response.isSuccessful() && response.getResult() == TriState.TRUE) {
return economy.accountAccessor().player().withUniqueId(targetPlayerId).get();
} else {
return CompletableFuture.failedFuture(new FailureException(response.getFailureReason()));
}
})
.thenCompose(response -> {
if (response.isSuccessful()) {
return response.getResult().doTransaction(
EconomyTransaction
.newBuilder()
.withCurrency(currency)
.withInitiator(initiator)
.withTransactionAmount(amount)
.withTransactionType(EconomyTransactionType.SET)
.build()
);
} else {
return CompletableFuture.failedFuture(new FailureException(response.getFailureReason()));
}
})
.thenCompose(response -> {
if (response.isSuccessful()) {
return CompletableFuture.completedFuture(response.getSuccess());
} else {
return CompletableFuture.failedFuture(new FailureException(response.getFailureReason()));
})
.whenComplete((bal, error) -> {
if (error != null) {
sender.sendMessage("Something went wrong!");
return;
}
sender.sendMessage("Success! New balance is " + bal.doubleValue() + "!");
});
}
This is actually less succinct than using EconomySubscriber#asFuture
- this is (ignoring an additional 7 lines from the fact that the original used a set balance, which we've removed direct access to for good reasons) 43 lines where using CFs for the Subscriber model was 32.
This can be simplified with a method that converts a Response into a CF:
public static <T> CompletableFuture<T> asFuture(Response<T> response) {
if (response.isSuccessful()) {
return CompletableFuture.completedFuture(response.getResult());
}
return CompletableFuture.failedFuture(new FailureException(response.getFailureReason()));
}
The down side is that this approach adds a .thenCompose(Response::asFuture)
on every call, which seems annoying long-term, just like constantly using EconomySubscriber#asFuture
was.
This results in a matching 32 (+7) lines:
Response with `Response::asFuture`
public void setBalance(
CommandSender sender, UUID targetPlayerId, BigDecimal amount, Currency currency
) {
EconomyTransactionInitiator<?> initiator;
if (sender instanceof Player) {
initiator = EconomyTransactionInitiator.createInitiator(EconomyTransactionInitiator.Type.PLAYER,
((Player) sender).getUniqueId()
);
} else {
initiator = EconomyTransactionInitiator.SERVER;
}
economy.hasAccount(AccountData.forPlayerAccount(targetPlayerId))
.thenCompose(Response::asFuture)
.thenCompose(val -> {
if (val == TriState.TRUE) {
return response.getResult().doTransaction(
EconomyTransaction
.newBuilder()
.withCurrency(currency)
.withInitiator(initiator)
.withTransactionAmount(amount)
.withTransactionType(EconomyTransactionType.SET)
.build()
);
} else {
return CompletableFuture.failedFuture(new FailureException("no account"));
}
})
.thenCompose(Response::asFuture)
.whenComplete((bal, error) -> {
if (error != null) {
sender.sendMessage("Something went wrong!");
return;
}
sender.sendMessage("Success! New balance is " + bal.doubleValue() + "!");
});
}
from treasury.
[...] help beginners instead of catering to them. Responses don't really hit that mark for me.
The API isn't purposely being catered towards any particular audience. It has and always will be suited towards more advanced users than Vault. This is because we have decided that we don't want to cater towards synchronous usage of methods which were designed to be used concurrently (- a decision I still agree with).
the Subscriber model was good for helping new users but had potential needless overhead
The adoption of the Subscriber model is something I campaigned for, and looking back, it's clearly a major flaw of the v1 APIs. My opinion on the Subscriber model is that it's inferior to CFs in many ways, and in particular, its verbosity and overall syntax made it more difficult to use than CFs. There were several other advantages discussed in the thread which concluded in v2 making a full pivot to CFs.
Lately I have been implementing Treasury v2 into my own economy provider, and I am relieved that we have switched to CFs. It's basically a testing ground for the APIs before we release them, I have found several issues doing so, most of which would have otherwise required breaking changes to patch (which won't be an option on our table upon v2's release).
Response - CF code
I'm not convinced that responses are required at all. Here's my opinion -
Advantages:
- Makes it easy for plugins to communicate basic errors, such as 'account can't afford this transaction'. These are distinguished from code/backend failures such as the database not being responsive.
Disadvantages:
-
To counter the only advantage I acknowledge, this can also be done via exceptions, which I've explained here.
-
CFs have inbuilt error handling. Wrapping something in a Result and then a CF is a double-layer of error handling which is unnecessary and increases code verbosity.
from treasury.
so what i propose is to create a RuntimeException that doesn't have a stacktrace that shall be used instead of Response and FailureReason.
^^^^^ Yes! This is exactly what I am looking for. @Jikoo, what do you think?
from treasury.
@Jikoo, if you're available, please see above. Thanks. :)
from treasury.
Thanks Jikoo!
It's interesting to note how we were originally using exceptions, and transitioned into Responses. See: #29
I would like to review the discussions on that Spigot thread before I confirm this.
from treasury.
On mobile and about to sleep so can't check. Are we able to make the treasury exception offer multilingual exception description messages? Otherwise they'll all be English which is probably undesirable.
from treasury.
If they're Exceptions they could be translated via ResourceBundle if we add a constructor accepting one, though I'll admit I'm not sure what Java version added that. Other than that it would be the economy provider's responsibility to provide translatability for their exceptions. That does unfortunately mean that they would be providing server-wide set translations rather than individualized translations. That was the intent behind the FailureReason initially - give API users a fixed set of cases to translate off of.
from treasury.
Unfortunately it was difficult to standardise with so many different use cases. Potentially treasury exceptions could have a method which gets the failure message with a locale argument?
from treasury.
Related Issues (20)
- Expanding #246
- General Javadoc Improvements
- `Response`: add method to fail with a given exception HOT 6
- `Account`: Change the return type of `retrievePermissionsMap` HOT 7
- Review API methods which do not return a Future+Response type. HOT 13
- `EconomyTransaction`: Rename some variables, getters and setters HOT 6
- `Currency`: Should we map display names against locales? HOT 3
- `Account`: Rename `setPermission`, `hasPermission` to use a plural HOT 3
- Review methods which return TriStates but do not utilise all three possible states HOT 25
- `Account`: add another `setPermissions` method which accepts a `Map<perm, state>` HOT 2
- Change currency identifier to NamespacedKey HOT 6
- EconomyTransactionResult HOT 6
- Add abstract TreasuryException#getMessage(Locale) method
- Publish to Hangar HOT 5
- Last thoughts on Treasury v2's API? YOUR OPINION is wanted! HOT 10
- Write API Documentation in Wiki HOT 17
- Strip colours out of logging HOT 1
- Breaking Change Proposals ('Treasury v3')
- New Author HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from treasury.