Open ViniMoraes opened 3 weeks ago
Fluxus::Safe::Caller
is designed to bring safety to the runtime. All errors are self-contained in the Fluxus::Results::Result
, and take the common aspect of a Failure
.
The Fluxus::Safe
is also optant of #on_exception
specific chainable method. This method makes the code more explicit about what to do after an expected error.
For example, during a payment flow, you can have a specific error type for gateway failures.
PayWithCreditCard.
call(credit_card: my_cc_object, payment_gateway_wrapper: my_wrapper)
on_success { |result| invoke_checkout_success(result) }.
on_exception(MyGateway::InsuficientFunds) { |result| invoke_checkout_failure(result) }
on_exception(MyGateway::FraudulentOperation) { |result| invoke_operational_fraud_and_report(result) }.
on_failure { |result| invoke_unexpected_failure(result) }
Not-mapped errors will always invoke failures, and guarantee the Ruby runtime continuity by taking wrapping the error in a "sandboxed" object. If you do not properly handle the Safe
object, silent failures will likely happen in your code.
If need to use the "let it crash" approach I strongly recommend you use the common Fluxus::Caller
.
Thank you for the detailed explanation. I understand the design philosophy behind Fluxus::Safe::Caller
and its focus on safety by encapsulating errors within a Failure
result. However, my concern is with the visibility of these errors during runtime. While the exceptions don’t need to interrupt the flow, having at least an optional log would greatly aid in debugging and monitoring. This log could help identify underlying issues without compromising the flow control that Safe::Caller
provides.
A suggestion would be to add a configurable logger in Safe::Caller
. Something like this:
module Fluxus
module Safe
class Caller < Runner
class << self
attr_writer :logger
def logger
@logger ||= Logger.new($stdout)
end
end
def self.call!(...)
instance = new
__call__(instance, ...)
rescue StandardError => e
raise e if e.is_a?(ResultTypeNotDefinedError)
logger&.error("Exception caught in #{name}: #{e.message}")
instance.Failure(type: :exception, result: { exception: e })
end
end
end
end
this add the possibility to configure a logger in Rails for example:
# config/initializers/fluxus_logger.rb
Fluxus::Safe::Caller.logger = Rails.logger
@ViniMoraes I see your concerns, and I'm open to evaluating a pull request implementing the proposed idea! I have a few suggestions before you, or anyone else start implementing it.
Fluxus
we must not make an instance of an object that is not needed. This change also solves the safe navigator suggestion.Fluxus
major instance will always serve the logger object without needing to have a Fluxus instance always memory loaded or constantly creating a logger change.Fluxus::Accessories::Logger
.Following those suggestions we are good to have it.
Another proposal based on Sidekiq implementation of death_handlers
, is receive a lambda on the Fluxus configuration that will be called every time an exception happens. The logic of what is going to be done when a exception happens will be kept outside of Fluxus, as the user can do more than just logging the message.
# config/initializers/fluxus.rb
Fluxus::Safe::Caller.exception_handler = ->(exception) { ... }
module Fluxus
module Safe
class Caller < Runner
class << self
attr_writer :logger
def logger
@logger ||= Logger.new($stdout)
end
end
def self.call!(...)
instance = new
__call__(instance, ...)
rescue StandardError => e
raise e if e.is_a?(ResultTypeNotDefinedError)
exception_handler.call(e) unless exception_handler.nil?
instance.Failure(type: :exception, result: { exception: e })
end
end
end
end
The Fluxus::Safe::Caller class handles errors by silencing them, which can lead to a lack of visibility into underlying issues. This behavior can make debugging difficult as errors are not logged or raised, potentially leading to unexpected behavior without a clear indication of the cause.
Proposed Solution:
Impact:
Improving error visibility will help developers identify and fix issues more efficiently, leading to more robust and maintainable code.