Nucleus Core
- Overview
- Components
- Supported Frameworks
- Quick start
- Best practices
- Support
- License
- Code of conduct
- Contribution guide
Overview
Nucleus Core defines a boundary between your business logic, and a framework.
Components
Responder - The boundary which passes request parameters to your business logic, then renders a response (requires framework request, and response adapaters).\ Operations - Service implementation that executes one side effect.\ Workflows - Service orchestration which composes complex, multi stage processes.\ Views - Presentation objects which render multiple formats.
Supported Frameworks
These packages implement request, and response adapters for their respective framework.
Getting started
- Install the gem
$ gem install nuclueus-core
- Initialize, and configure
NucleusCore
require "nucleus-core"
NucleusCore.configure do |config|
config.logger = Logger.new($stdout)
config.default_response_format = :json
config.exceptions = {
not_found: ActiveRecord::RecordNotFound,
unprocessible: [ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved],
bad_request: Apipie::ParamError,
unauthorized: Pundit::NotAuthorizedError
}
end
- Create a class that implements the rendering methods below. Class, or instance methods can be used, make sure to initialize the responder accordingly.
class ResponderAdapter
# entity: Nucleus::ResponseAdapter
def render_json(entity)
end
def render_xml(entity)
end
def render_pdf(entity)
end
def render_csv(entity)
end
def render_text(entity)
end
def render_nothing(entity)
end
end
- Create a class that implements
call
which returns a hash of request details.format
andparameters
are required, but there others can be returned.
class RequestAdapter
def call(args={})
{ format: args[:format], parameters: args[:params], ...}
end
end
- Implement your business logic using Operations, and orchestrate more complex proceedures with Workflows.
operations/fetch_order.rb
class Operations::FetchOrder < NucleusCore::Operation
def call
context.order = find_order(context.id)
rescue NucleusCore::NotFound => e
context.fail!(e., exception: e)
end
end
operations/apply_order_discount.rb
class Operations::ApplyOrderDiscount < NucleusCore::Operation
def call
discount = context.discount || 0.25
order = update_order(context.order, discount: discount)
context.order = order
rescue NucleusCore::NotFound, NucleusCore::Unprocessable => e
context.fail!(e., exception: e)
end
end
workflows/fulfill_order.rb
class Workflows::FulfillOrder < NucleusCore::Workflow
def define
start_node(continue: :apply_discount?)
register_node(
state: :apply_discount?,
operation: Operations::FetchOrder,
determine_signal: ->(context) { context.order.total > 10 ? :discount : :pay },
signals: { discount: :discount_order, pay: :take_payment }
)
register_node(
state: :discount_order,
operation: Operations::ApplyOrderDiscount,
signals: { continue: :take_payment }
)
register_node(
state: :take_payment,
operation: ->(context) { context.paid = context.order.pay! },
signals: { continue: :completed }
)
register_node(
state: :completed,
determine_signal: ->(_) { :wait }
)
end
end
- Define your view, and it's responses.
views/order.rb
class Views::Order < NucleusCore::View
def initialize(order)
super(id: order.id, price: "$#{order.total}", paid: order.paid, created_at: order.created_at)
end
def json_response
content = {
payment: {
id: id,
price: price,
paid: paid,
created_at: created_at,
signature: SecureRandom.hex
}
}
NucleusCore::JsonResponse.new(content: content)
end
def pdf_response
pdf = generate_pdf(id, price, paid)
NucleusCore::PdfResponse.new(content: pdf)
end
end
- Initialize the responder with your adapters, then call your business logic, and return a view.
controllers/orders_controller.rb
class OrdersController
before_action do
@responder = Nucleus::Responder.new(
response_adapter: ResponseAdapter.new,
request_adapter: RequestAdapter.new
)
@request = {
format: request.format,
parameters: request.params
}
end
def create
@responder.execute(@request) do |req|
context, _process = Workflows::FulfillOrder.call(context: req.parameters)
return Views::Order.new(order: context.order) if context.success?
return context
end
end
end
- Then tell us about it!
Support
If you want to report a bug, or have ideas, feedback or questions about the gem, let me know via GitHub issues and we will do our best to provide a helpful answer.
License
The gem is available as open source under the terms of the MIT License.
Code of conduct
Everyone interacting in this project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Contribution guide
Pull requests are welcome!