Skip to content

Commit

Permalink
Add configuration option to service
Browse files Browse the repository at this point in the history
  • Loading branch information
casiodk committed Jan 30, 2024
1 parent 5d8a778 commit 2fa97c6
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 45 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## v0.1.4
January 30 2024

## v0.1.3
September 18 2023

Expand All @@ -11,4 +14,3 @@ March 03 2022
- Add the response helper.
Refactor the code a bit :)
Include Dockerfile and docker-compose

3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Base image
FROM ruby:3.2.1-slim
FROM ruby:3.2.3-bullseye

# Add our Gemfile and install gems
ADD Gemfile* ./
ADD hanikamu-base_service.gemspec ./


RUN bundle check || bundle install

WORKDIR "/app"
16 changes: 12 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
PATH
remote: .
specs:
hanikamu-service (0.1.3)
hanikamu-service (0.1.4)
dry-configurable (~> 1.1.0)
dry-monads (~> 1.6.0)
dry-struct (~> 1.6.0)

Expand All @@ -10,9 +11,13 @@ GEM
specs:
ast (2.4.2)
base64 (0.1.1)
bigdecimal (3.1.6)
coderay (1.1.3)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
diff-lcs (1.5.0)
dry-configurable (1.1.0)
dry-core (~> 1.0, < 2)
zeitwerk (~> 2.6)
dry-core (1.0.1)
concurrent-ruby (~> 1.0)
zeitwerk (~> 2.6)
Expand All @@ -30,7 +35,8 @@ GEM
dry-types (>= 1.7, < 2)
ice_nine (~> 0.11)
zeitwerk (~> 2.6)
dry-types (1.7.1)
dry-types (1.7.2)
bigdecimal (~> 3.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.0)
dry-inflector (~> 1.0)
Expand Down Expand Up @@ -92,9 +98,11 @@ GEM
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
unicode-display_width (2.4.2)
zeitwerk (2.6.11)
zeitwerk (2.6.12)

PLATFORMS
aarch64-linux
arm64-darwin-23
x86_64-darwin-20
x86_64-linux
x86_64-linux-musl
Expand Down
52 changes: 29 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@
#### Definition
Services enforce a pattern design as service objects.

#### Why?
- We want SRP(single responsibility classes) for easier testing.

- We want to have a common pattern on our service design preventing a fragmentation of patterns.

- We want to avoid business logic in our models.

- We want to test our business logic in isolation.

- We want a complete overview over our domain model and business logic transactions

- We want to express intent over data structure.
#### Objectives
- Adherence to the Single Responsibility Principle for simplified testing.
- Establishment of a uniform pattern for service design to prevent divergent practices.
- Removal of business logic from models to maintain clean architecture.
- Isolation of business logic for focused testing.
- Comprehensive understanding of domain models and business transactions.
- Emphasis on clear intent rather than mere data structuring.


#### Principles
Expand All @@ -27,11 +22,11 @@ Services enforce a pattern design as service objects.

- A `Service.call!` will success on it's task or will raise an error

- `.call!` will raise a set of known errors(validation, ...)
- `.call!` will raise a predefined set of errors (e.g., validation errors).

- `Service.call` will success on it's task returning a monadic Success reponse or will fail returning a monadic Failure
- `Service.call` should either successfully return a Success response or a Failure response in a monadic format.

- `.call` will only catch a set of known Service Errors inheriting from Hanikamu::Service::WhiteListedError and it's implemented as a wrapper of `.call!`
- `.call` will catch only specific Service Errors inheriting from Hanikamu::Service::WhiteListedError and it's implemented as a wrapper of `.call!`


#### Responsibilities
Expand All @@ -58,13 +53,12 @@ Services enforce a pattern design as service objects.
- When asking for a banana return only the banana
https://www.johndcook.com/blog/2011/07/19/you-wanted-banana/

###### Schema
###### Schema Validation
Checks if the input types are valid.
E.g. job_id is required and of type integer
Errors can be corrected by the client passing different input types to the operation

#### Example
- Validates input types (e.g., ensuring job_id is an integer and required).
- Errors can be corrected by the client passing different input types to the service.

#### Example Usage

```ruby
module Types
Expand Down Expand Up @@ -98,13 +92,25 @@ Errors can be corrected by the client passing different input types to the opera
monadic_response.success if monadic_response.success?
```


#### Configuration

If you wish to include additional error classes to the existing whitelisted_errors array, which is used for determining which errors should result in a Failure response when calling services without a bang (e.g., SomeService.call), you can do so by appending these classes to the whitelisted_errors list in an initializer.

```ruby

# in initializer config/initializers/hanikamu_service

Hanikamu::Service.configure do |config|
config.whitelisted_errors = [SomeError]
end

```

#### Using docker

Rename Makefile.example to Makefile
- `make build` for building the image
- `make shell` to get a shell console with the ruby environment
- `make console` get a ruby console
- `make rspec` run the specs



2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.3
0.1.4
14 changes: 4 additions & 10 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
version: "3"
version: "3.6"
networks:
docker-compose-example-tier:
hanikamu-service-network:
driver: bridge
services:
app:
build:
context: .
dockerfile: Dockerfile
build: .
volumes:
- .:/app
networks:
- docker-compose-example-tier
volumes:
- .:/app
networks:
- docker-compose-example-tier
- hanikamu-service-network
3 changes: 2 additions & 1 deletion hanikamu-base_service.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ $LOAD_PATH.push File.expand_path("lib", __dir__)

Gem::Specification.new do |s|
s.name = "hanikamu-service"
s.version = "0.1.3"
s.version = "0.1.4"
s.authors = ["Nicolai Seerup", "Alejandro Jimenez"]
s.summary = "This is the base service for all pattern designs used in hanikamu design"
s.required_ruby_version = ">= 3.1"
Expand All @@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.files = Dir["{config,lib}/**/*", "Rakefile"]
s.require_paths = ["lib"]

s.add_dependency "dry-configurable", "~> 1.1.0"
s.add_dependency "dry-monads", "~> 1.6.0"
s.add_dependency "dry-struct", "~> 1.6.0"

Expand Down
6 changes: 5 additions & 1 deletion lib/hanikamu/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
module Hanikamu
# :nodoc
class Service < Dry::Struct
extend Dry::Configurable
extend Dry::Monads[:result]
Error = Class.new(StandardError)

setting :whitelisted_errors, default: []

class << self
def call(options = {})
Success(call!(options))
Expand All @@ -27,7 +30,8 @@ def call!(options = {})
def whitelisted_error?(error_klass)
error_klass == Hanikamu::Service::Error ||
error_klass.superclass == Hanikamu::Service::Error ||
error_klass == Dry::Struct::Error
error_klass == Dry::Struct::Error ||
config.whitelisted_errors.include?(error_klass)
end
end

Expand Down
40 changes: 38 additions & 2 deletions spec/hanikamu/service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

require "spec_helper"

module Types
include Dry.Types()
end

RSpec.describe Hanikamu::Service do
describe "#new" do
it "is private" do
Expand All @@ -10,13 +14,45 @@
end
end

describe ".config" do
describe "#whitelisted_errors" do
let(:error) { Class.new(StandardError) }
let(:service_with_error) do
Class.new(described_class) do
attribute :error, Types::Strict::Class

def call!
raise error, "Oh, no!"
end

define_singleton_method(:name) { "RSpecServiceWithError" }
end
end

it "raises an error when the error is not whitelisted" do
expect { service_with_error.call(error:) }.to raise_error(error)
end

context "when the error is whitelisted" do
before do
described_class.configure do |config|
config.whitelisted_errors = [error]
end
end

it "returns a Failure object when the error is whitelisted" do
expect(service_with_error.call(error:)).to be_a(Dry::Monads::Failure)
end
end
end
end

describe "#call!" do
it "has to be declared in subclasses" do
expect { described_class.call! }.to raise_error(NoMethodError)
end
end


context "when passing the wrong argument type" do
let(:failing_service) do
class TestFooModule::Bar < Hanikamu::Service
Expand Down Expand Up @@ -67,7 +103,7 @@ def call!
end
end

context "when raising a custom error inheriting from Hanikamu::Service::Error" do
context "when raising a custom error inheriting from Hanikamu::Service::Error" do
let(:failing_service) do
class TestFooModule::Bar < Hanikamu::Service
CustomError = Class.new(self::Error)
Expand Down

0 comments on commit 2fa97c6

Please sign in to comment.