├── .env.development.sample ├── .env.test ├── .env.test.sample ├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── apps └── http │ ├── actions │ └── health │ │ └── get.rb │ └── app.rb ├── config.ru ├── config └── enviroment.rb ├── db └── migrate │ └── 20200806183613_create_tests.rb ├── lib ├── core │ ├── operation.rb │ └── types.rb └── persistance │ ├── relations │ └── test.rb │ └── repository.rb ├── services └── accounts │ ├── entities │ └── account.rb │ ├── operations │ └── show.rb │ └── repositories │ └── account.rb ├── spec ├── factories │ └── test.rb ├── services │ └── accounts │ │ ├── operations │ │ └── show_spec.rb │ │ └── repositories │ │ └── account_spec.rb └── spec_helper.rb └── system ├── boot ├── db.rb ├── logger.rb ├── persistence.rb └── settings.rb ├── container.rb └── registration.rb /.env.development.sample: -------------------------------------------------------------------------------- 1 | PROJECT_ENV="development" 2 | PROJECT_APPS="all" 3 | DATABASE_URL="postgres://localhost/service_development" 4 | 5 | LOGGER_JSON_FORMATTER="false" 6 | LOGGER_LEVEl="info" 7 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | PROJECT_ENV="test" 2 | PROJECT_APPS="all" 3 | DATABASE_URL="postgres://localhost/service_test" 4 | 5 | LOGGER_JSON_FORMATTER="false" 6 | LOGGER_LEVEl="info" 7 | -------------------------------------------------------------------------------- /.env.test.sample: -------------------------------------------------------------------------------- 1 | PROJECT_ENV="test" 2 | PROJECT_APPS="all" 3 | DATABASE_URL="postgres://localhost/service_test" 4 | 5 | LOGGER_JSON_FORMATTER="false" 6 | LOGGER_LEVEl="info" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public/assets* 2 | /tmp 3 | /coverage 4 | .idea 5 | .ruby-version 6 | .env.dev 7 | .env.development 8 | .env.test 9 | .bundle 10 | 11 | .direnv/ 12 | .envrc 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | ruby '2.7.0' 6 | 7 | # http layer 8 | gem 'hanami-api' 9 | gem 'hanami-controller', git: 'https://github.com/hanami/controller.git', tag: 'v2.0.0.alpha1' 10 | gem 'puma', '~> 3.12.4' 11 | 12 | # persistance layer 13 | gem 'pg' 14 | gem 'rom', '~> 5.2.4' 15 | gem 'rom-sql', '~> 3.2.0' 16 | gem 'sequel', '~> 4.49.0' 17 | 18 | # Monitoring and logging 19 | gem 'semantic_logger' 20 | 21 | # dependency managment 22 | gem 'dry-system' 23 | 24 | # business logic section 25 | gem 'dry-monads' 26 | gem 'dry-validation' 27 | 28 | # Other 29 | gem 'bigdecimal', '1.4.2' 30 | gem 'dotenv', '~> 2.4' 31 | gem 'rake' 32 | 33 | group :development do 34 | end 35 | 36 | group :test, :development do 37 | # data generation and cleanup 38 | gem 'database_cleaner' 39 | gem 'database_cleaner-sequel' 40 | 41 | # style check 42 | gem 'rubocop', require: false 43 | gem 'rubocop-rspec' 44 | end 45 | 46 | group :test do 47 | gem 'capybara' 48 | gem 'rspec' 49 | gem 'simplecov', require: false 50 | gem 'simplecov-json', require: false 51 | 52 | gem 'rom-factory' 53 | end 54 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/hanami/controller.git 3 | revision: 5993ee96408e70c3b2365aec1ff91940cdae7c91 4 | tag: v2.0.0.alpha1 5 | specs: 6 | hanami-controller (2.0.0.alpha1) 7 | hanami-utils (~> 2.0.alpha) 8 | rack (~> 2.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | addressable (2.7.0) 14 | public_suffix (>= 2.0.2, < 5.0) 15 | ast (2.4.1) 16 | bigdecimal (1.4.2) 17 | capybara (3.33.0) 18 | addressable 19 | mini_mime (>= 0.1.3) 20 | nokogiri (~> 1.8) 21 | rack (>= 1.6.0) 22 | rack-test (>= 0.6.3) 23 | regexp_parser (~> 1.5) 24 | xpath (~> 3.2) 25 | concurrent-ruby (1.1.7) 26 | database_cleaner (1.8.5) 27 | database_cleaner-sequel (1.8.0) 28 | database_cleaner (~> 1.8.0) 29 | sequel 30 | diff-lcs (1.4.4) 31 | docile (1.3.2) 32 | dotenv (2.7.6) 33 | dry-auto_inject (0.7.0) 34 | dry-container (>= 0.3.4) 35 | dry-configurable (0.11.6) 36 | concurrent-ruby (~> 1.0) 37 | dry-core (~> 0.4, >= 0.4.7) 38 | dry-equalizer (~> 0.2) 39 | dry-container (0.7.2) 40 | concurrent-ruby (~> 1.0) 41 | dry-configurable (~> 0.1, >= 0.1.3) 42 | dry-core (0.4.9) 43 | concurrent-ruby (~> 1.0) 44 | dry-equalizer (0.3.0) 45 | dry-inflector (0.2.0) 46 | dry-initializer (3.0.3) 47 | dry-logic (1.0.7) 48 | concurrent-ruby (~> 1.0) 49 | dry-core (~> 0.2) 50 | dry-equalizer (~> 0.2) 51 | dry-monads (1.3.5) 52 | concurrent-ruby (~> 1.0) 53 | dry-core (~> 0.4, >= 0.4.4) 54 | dry-equalizer 55 | dry-schema (1.5.4) 56 | concurrent-ruby (~> 1.0) 57 | dry-configurable (~> 0.8, >= 0.8.3) 58 | dry-core (~> 0.4) 59 | dry-equalizer (~> 0.2) 60 | dry-initializer (~> 3.0) 61 | dry-logic (~> 1.0) 62 | dry-types (~> 1.4) 63 | dry-struct (1.3.0) 64 | dry-core (~> 0.4, >= 0.4.4) 65 | dry-equalizer (~> 0.3) 66 | dry-types (~> 1.3) 67 | ice_nine (~> 0.11) 68 | dry-system (0.18.1) 69 | concurrent-ruby (~> 1.0) 70 | dry-auto_inject (>= 0.4.0) 71 | dry-configurable (~> 0.11, >= 0.11.1) 72 | dry-container (~> 0.7, >= 0.7.2) 73 | dry-core (~> 0.3, >= 0.3.1) 74 | dry-equalizer (~> 0.2) 75 | dry-inflector (~> 0.1, >= 0.1.2) 76 | dry-struct (~> 1.0) 77 | dry-types (1.4.0) 78 | concurrent-ruby (~> 1.0) 79 | dry-container (~> 0.3) 80 | dry-core (~> 0.4, >= 0.4.4) 81 | dry-equalizer (~> 0.3) 82 | dry-inflector (~> 0.1, >= 0.1.2) 83 | dry-logic (~> 1.0, >= 1.0.2) 84 | dry-validation (1.5.6) 85 | concurrent-ruby (~> 1.0) 86 | dry-container (~> 0.7, >= 0.7.1) 87 | dry-core (~> 0.4) 88 | dry-equalizer (~> 0.2) 89 | dry-initializer (~> 3.0) 90 | dry-schema (~> 1.5, >= 1.5.2) 91 | faker (2.13.0) 92 | i18n (>= 1.6, < 2) 93 | hanami-api (0.1.1) 94 | hanami-router (~> 2.0.alpha) 95 | hanami-router (2.0.0.alpha3) 96 | mustermann (~> 1.0) 97 | mustermann-contrib (~> 1.0) 98 | rack (~> 2.0) 99 | hanami-utils (2.0.0.alpha1) 100 | concurrent-ruby (~> 1.0) 101 | transproc (~> 1.0) 102 | hansi (0.2.0) 103 | i18n (1.8.5) 104 | concurrent-ruby (~> 1.0) 105 | ice_nine (0.11.2) 106 | json (2.3.1) 107 | mini_mime (1.0.2) 108 | mini_portile2 (2.4.0) 109 | mustermann (1.1.1) 110 | ruby2_keywords (~> 0.0.1) 111 | mustermann-contrib (1.1.1) 112 | hansi (~> 0.2.0) 113 | mustermann (= 1.1.1) 114 | nokogiri (1.10.10) 115 | mini_portile2 (~> 2.4.0) 116 | parallel (1.19.2) 117 | parser (2.7.1.4) 118 | ast (~> 2.4.1) 119 | pg (1.2.3) 120 | public_suffix (4.0.6) 121 | puma (3.12.6) 122 | rack (2.2.3) 123 | rack-test (1.1.0) 124 | rack (>= 1.0, < 3) 125 | rainbow (3.0.0) 126 | rake (13.0.1) 127 | regexp_parser (1.7.1) 128 | rexml (3.2.4) 129 | rom (5.2.4) 130 | rom-changeset (~> 5.2, >= 5.2.3) 131 | rom-core (~> 5.2, >= 5.2.3) 132 | rom-repository (~> 5.2, >= 5.2.2) 133 | rom-changeset (5.2.3) 134 | dry-core (~> 0.4) 135 | rom-core (~> 5.2) 136 | transproc (~> 1.0, >= 1.1.0) 137 | rom-core (5.2.3) 138 | concurrent-ruby (~> 1.1) 139 | dry-container (~> 0.7) 140 | dry-core (~> 0.4) 141 | dry-equalizer (~> 0.2) 142 | dry-inflector (~> 0.1) 143 | dry-initializer (~> 3.0, >= 3.0.1) 144 | dry-struct (~> 1.0) 145 | dry-types (~> 1.0) 146 | transproc (~> 1.0, >= 1.1.0) 147 | rom-factory (0.10.2) 148 | dry-configurable (~> 0.7) 149 | dry-core (~> 0.4) 150 | faker (>= 1.7, < 3.0) 151 | rom-core (~> 5.0) 152 | rom-repository (5.2.2) 153 | dry-core (~> 0.4) 154 | dry-initializer (~> 3.0, >= 3.0.1) 155 | rom-core (~> 5.2, >= 5.2.2) 156 | rom-sql (3.2.0) 157 | dry-core (~> 0.4) 158 | dry-equalizer (~> 0.2) 159 | dry-types (~> 1.0) 160 | rom-core (~> 5.2, >= 5.2.1) 161 | sequel (>= 4.49) 162 | rspec (3.9.0) 163 | rspec-core (~> 3.9.0) 164 | rspec-expectations (~> 3.9.0) 165 | rspec-mocks (~> 3.9.0) 166 | rspec-core (3.9.2) 167 | rspec-support (~> 3.9.3) 168 | rspec-expectations (3.9.2) 169 | diff-lcs (>= 1.2.0, < 2.0) 170 | rspec-support (~> 3.9.0) 171 | rspec-mocks (3.9.1) 172 | diff-lcs (>= 1.2.0, < 2.0) 173 | rspec-support (~> 3.9.0) 174 | rspec-support (3.9.3) 175 | rubocop (0.91.0) 176 | parallel (~> 1.10) 177 | parser (>= 2.7.1.1) 178 | rainbow (>= 2.2.2, < 4.0) 179 | regexp_parser (>= 1.7) 180 | rexml 181 | rubocop-ast (>= 0.4.0, < 1.0) 182 | ruby-progressbar (~> 1.7) 183 | unicode-display_width (>= 1.4.0, < 2.0) 184 | rubocop-ast (0.4.0) 185 | parser (>= 2.7.1.4) 186 | rubocop-rspec (1.43.2) 187 | rubocop (~> 0.87) 188 | ruby-progressbar (1.10.1) 189 | ruby2_keywords (0.0.2) 190 | semantic_logger (4.7.2) 191 | concurrent-ruby (~> 1.0) 192 | sequel (4.49.0) 193 | simplecov (0.19.0) 194 | docile (~> 1.1) 195 | simplecov-html (~> 0.11) 196 | simplecov-html (0.12.2) 197 | simplecov-json (0.2.1) 198 | json 199 | simplecov 200 | transproc (1.1.1) 201 | unicode-display_width (1.7.0) 202 | xpath (3.2.0) 203 | nokogiri (~> 1.8) 204 | 205 | PLATFORMS 206 | ruby 207 | 208 | DEPENDENCIES 209 | bigdecimal (= 1.4.2) 210 | capybara 211 | database_cleaner 212 | database_cleaner-sequel 213 | dotenv (~> 2.4) 214 | dry-monads 215 | dry-system 216 | dry-validation 217 | hanami-api 218 | hanami-controller! 219 | pg 220 | puma (~> 3.12.4) 221 | rake 222 | rom (~> 5.2.4) 223 | rom-factory 224 | rom-sql (~> 3.2.0) 225 | rspec 226 | rubocop 227 | rubocop-rspec 228 | semantic_logger 229 | sequel (~> 4.49.0) 230 | simplecov 231 | simplecov-json 232 | 233 | RUBY VERSION 234 | ruby 2.7.0p0 235 | 236 | BUNDLED WITH 237 | 2.1.4 238 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2020 Anton Davydov. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ruby (Micro) Service Template 2 | 3 | Simple service template based on dry-system, hanami-api, hanami 2.0, rom, rabbitmq, kafka, psql and other microlibraries. 4 | 5 | ## Motivation 6 | 7 | It will be awesome to use template repository as a [service chassis](https://microservices.io/patterns/microservice-chassis.html). 8 | 9 | ## Technologies 10 | 11 | * `hanami-api` + `hanami controller 2.0` as a http transport layer 12 | * `dry-system` as a dependency managment framework 13 | 14 | ## Resource usage 15 | 16 | ## How to use this template 17 | 18 | ## File structure 19 | 20 | ## Contributing 21 | 22 | ## Copyright 23 | 24 | Released under MIT License. 25 | 26 | Copyright © 2020 Anton Davydov. 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['PROJECT_APPS'] = 'rake' 4 | require_relative 'config/enviroment' 5 | require 'rom-sql' 6 | require 'rom/sql/rake_task' 7 | 8 | namespace :db do 9 | task :setup do 10 | Container.start(:db) 11 | 12 | ROM::SQL::RakeSupport.env = ROM.container(Container['db.config']) do |config| 13 | config.gateways[:default].use_logger(Container[:logger]) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /apps/http/actions/health/get.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'hanami/action' 4 | 5 | module HTTP 6 | module Actions 7 | module Health 8 | class Get < Hanami::Action 9 | include Import[ 10 | 'hanami.action.configuration' 11 | ] 12 | 13 | def handle(_req, res) 14 | res.status = 200 15 | res.body = 'OK' 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /apps/http/app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'hanami/api' 4 | require 'hanami/action' 5 | require 'hanami/controller/configuration' 6 | 7 | # HACK: We need it for actions, it will be better to put it to system/boot/ folder. 8 | # But I found some dependency booting issue 9 | # 10 | # Docs for hanami actions: 11 | # https://github.com/hanami/controller/tree/4bbf1a046f3fa567db77d4cfc1d400fd330f7daa 12 | 13 | Container.register('hanami.action.configuration', Hanami::Controller::Configuration.new do |config| 14 | config.default_response_format = :json 15 | end) 16 | 17 | module HTTP 18 | class App < Hanami::API 19 | get '/health', to: Container['http.actions.health.get'] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | PROJECT_APP = 'http' 4 | require 'bundler/setup' 5 | 6 | require_relative 'config/enviroment' 7 | 8 | run Container['http.app'] 9 | -------------------------------------------------------------------------------- /config/enviroment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dotenv' 4 | Dotenv.load('.env', ".env.#{ENV['PROJECT_ENV']}") 5 | 6 | require_relative '../system/container' 7 | 8 | Container.finalize! 9 | -------------------------------------------------------------------------------- /db/migrate/20200806183613_create_tests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ROM::SQL.migration do 4 | change do 5 | create_table :tests do 6 | primary_key :id 7 | 8 | column :errors, :jsonb, default: '{}' 9 | 10 | column :created_at, DateTime, null: false 11 | column :updated_at, DateTime, null: false 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/core/operation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry/validation' 4 | require 'dry/monads' 5 | require 'dry/monads/do' 6 | 7 | # Base operation class. Provides dry-monads do notation and Result monads. 8 | # 9 | # @api private 10 | # 11 | # @see http://dry-rb.org/gems/dry-monads/1.0/result/ Result monad documentation 12 | # @see http://dry-rb.org/gems/dry-monads/1.0/do-notation/ Do notation documentation 13 | # 14 | # @example 15 | # 16 | # module Operations 17 | # class Read < Core::Operation 18 | # def call(payload) 19 | # payload = yield Success(payload) 20 | # Success(payload) 21 | # end 22 | # end 23 | # end 24 | # 25 | # Operations::Read.new.call(a: 1) # => Success(a: 1) 26 | module Core 27 | class Operation 28 | include Dry::Monads[:try, :result] 29 | include Dry::Monads::Do.for(:call) 30 | 31 | Dry::Validation.load_extensions(:monads) 32 | 33 | def call(*) 34 | raise NotImplementedError 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/core/types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry-types' 4 | 5 | # Module with all project types 6 | # 7 | # {http://dry-rb.org/gems/dry-types/ Dry-types documentation} 8 | module Core 9 | module Types 10 | include Dry.Types() 11 | 12 | # System types 13 | LoggerLevel = Symbol.constructor(proc { |value| value.to_s.downcase.to_sym }) 14 | .default(:info) 15 | .enum(:trace, :unknown, :error, :fatal, :warn, :info, :debug) 16 | 17 | ProjectApps = Array.constructor(proc { |value| value.to_s.downcase.split(',') }).of(Types::String) 18 | 19 | UUID = Strict::String.constrained( 20 | format: /\A(\h{32}|\h{8}-\h{4}-\h{4}-\h{4}-\h{12})\z/ 21 | ) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/persistance/relations/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Persistance 4 | module Relations 5 | class Test < ROM::Relation[:sql] 6 | schema(:tests, infer: true) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/persistance/repository.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Persistance 4 | class Repository < ROM::Repository::Root 5 | include Import['persistence.container'] 6 | 7 | commands :create, update: :by_pk, 8 | use: :timestamps, 9 | plugins_options: { 10 | timestamps: { 11 | timestamps: %i(created_at updated_at) 12 | } 13 | } 14 | 15 | def find(id) 16 | root.by_pk(id).one 17 | end 18 | 19 | def all 20 | root.to_a 21 | end 22 | 23 | def count 24 | root.count 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /services/accounts/entities/account.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | module Entities 5 | class Account < ROM::Struct 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /services/accounts/operations/show.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | module Operations 5 | class Show < Core::Operation 6 | include Import[ 7 | :logger, 8 | repo: 'accounts.repositories.account' 9 | ] 10 | 11 | def call(id:) 12 | entity = repo.find(id) 13 | Success(entity) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /services/accounts/repositories/account.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Accounts 4 | module Repositories 5 | class Account < Persistance::Repository[:tests] 6 | struct_namespace Accounts::Entities 7 | 8 | commands :create, update: :by_pk, use: :timestamps, 9 | plugins_options: { timestamps: { timestamps: %i(created_at updated_at) } } 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/factories/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Docs: https://rom-rb.org/learn/factory/0.10/ 4 | Factory.define(:test) do |f| 5 | f.errors { { a: 1, b: 2 } } 6 | 7 | f.timestamps 8 | end 9 | -------------------------------------------------------------------------------- /spec/services/accounts/operations/show_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Accounts::Operations::Show, type: :operation do 4 | subject { operation.call(id: id) } 5 | 6 | let(:id) { 1 } 7 | 8 | let(:operation) { described_class.new(repo: repo) } 9 | 10 | let(:repo) { instance_double('Accounts::Repositories::Account', find: entity) } 11 | 12 | context 'when account exists in database' do 13 | let(:entity) { Factory.structs[:test] } 14 | 15 | it { expect(subject).to be_success } 16 | it { expect(subject.value!).to eq(entity) } 17 | end 18 | 19 | context 'when employee does not exist in database' do 20 | let(:entity) { nil } 21 | 22 | it { expect(subject).to be_success } 23 | end 24 | 25 | context 'with real dependencies' do 26 | let(:operation) { described_class.new } 27 | 28 | let(:id) { Factory[:test].id } 29 | 30 | it { expect(subject).to be_success } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/services/accounts/repositories/account_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Accounts::Repositories::Account, type: :repository do 4 | let(:repo) { described_class.new } 5 | 6 | describe "#somethings" do 7 | pending "Not implemented" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Require this file for unit tests 4 | ENV['PROJECT_ENV'] ||= 'test' 5 | ENV['PROJECT_APP'] = 'all' 6 | 7 | require 'simplecov' 8 | require 'simplecov-json' 9 | SimpleCov.start 10 | 11 | SimpleCov.formatters = [ 12 | SimpleCov::Formatter::HTMLFormatter, 13 | SimpleCov::Formatter::JSONFormatter 14 | ] 15 | 16 | SimpleCov.start do 17 | add_filter '/lib/core/' 18 | add_filter '/spec/' 19 | add_filter '/config/' 20 | add_filter '/system/' 21 | end 22 | 23 | require_relative '../config/enviroment' 24 | 25 | # rom-factories initialization 26 | require 'rom/factory' 27 | Factory = ROM::Factory.configure do |config| 28 | config.rom = Container['persistence.container'] 29 | end 30 | 31 | require 'database_cleaner' 32 | DatabaseCleaner.allow_remote_database_url = true 33 | DatabaseCleaner[:sequel].db = Container['persistence.container'].gateways[:default].connection 34 | DatabaseCleaner[:sequel].strategy = :transaction 35 | 36 | Dir[File.dirname(__FILE__) + '/factories/*.rb'].each { |file| require file } # rubocop:disable Lint/NonDeterministicRequireOrder 37 | 38 | # This file was generated by the `rspec --init` command. Conventionally, all 39 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 40 | # The generated `.rspec` file contains `--require spec_helper` which will cause 41 | # this file to always be loaded, without a need to explicitly require it in any 42 | # files. 43 | # 44 | # Given that it is always loaded, you are encouraged to keep this file as 45 | # light-weight as possible. Requiring heavyweight dependencies from this file 46 | # will add to the boot time of your test suite on EVERY test run, even for an 47 | # individual file that may not need all of that loaded. Instead, consider making 48 | # a separate helper file that requires the additional dependencies and performs 49 | # the additional setup, and require it from the spec files that actually need 50 | # it. 51 | # 52 | # The `.rspec` file also contains a few flags that are not defaults but that 53 | # users commonly want. 54 | # 55 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 56 | RSpec.configure do |config| 57 | # rspec-expectations config goes here. You can use an alternate 58 | # assertion/expectation library such as wrong or the stdlib/minitest 59 | # assertions if you prefer. 60 | config.expect_with :rspec do |expectations| 61 | # This option will default to `true` in RSpec 4. It makes the `description` 62 | # and `failure_message` of custom matchers include text for helper methods 63 | # defined using `chain`, e.g.: 64 | # be_bigger_than(2).and_smaller_than(4).description 65 | # # => "be bigger than 2 and smaller than 4" 66 | # ...rather than: 67 | # # => "be bigger than 2" 68 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 69 | end 70 | 71 | # rspec-mocks config goes here. You can use an alternate test double 72 | # library (such as bogus or mocha) by changing the `mock_with` option here. 73 | config.mock_with :rspec do |mocks| 74 | # Prevents you from mocking or stubbing a method that does not exist on 75 | # a real object. This is generally recommended, and will default to 76 | # `true` in RSpec 4. 77 | mocks.verify_partial_doubles = true 78 | end 79 | 80 | # The settings below are suggested to provide a good initial experience 81 | # with RSpec, but feel free to customize to your heart's content. 82 | # # These two settings work together to allow you to limit a spec run 83 | # # to individual examples or groups you care about by tagging them with 84 | # # `:focus` metadata. When nothing is tagged with `:focus`, all examples 85 | # # get run. 86 | # config.filter_run :focus 87 | # config.run_all_when_everything_filtered = true 88 | # 89 | # # Allows RSpec to persist some state between runs in order to support 90 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 91 | # # you configure your source control system to ignore this file. 92 | # config.example_status_persistence_file_path = "spec/examples.txt" 93 | # 94 | # # Limits the available syntax to the non-monkey patched syntax that is 95 | # # recommended. For more details, see: 96 | # # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 97 | # # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 98 | # # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 99 | # config.disable_monkey_patching! 100 | # 101 | # # This setting enables warnings. It's recommended, but in many cases may 102 | # # be too noisy due to issues in dependencies. 103 | # config.warnings = false 104 | # 105 | # # Many RSpec users commonly either run the entire suite or an individual 106 | # # file, and it's useful to allow more verbose output when running an 107 | # # individual spec file. 108 | # if config.files_to_run.one? 109 | # # Use the documentation formatter for detailed output, 110 | # # unless a formatter has already been configured 111 | # # (e.g. via a command-line flag). 112 | # config.default_formatter = 'doc' 113 | # end 114 | # 115 | # # Print the 10 slowest examples and example groups at the 116 | # # end of the spec run, to help surface which specs are running 117 | # # particularly slow. 118 | # config.profile_examples = 10 119 | # 120 | # # Run specs in random order to surface order dependencies. If you find an 121 | # # order dependency and want to debug it, you can fix the order by providing 122 | # # the seed, which is printed after each run. 123 | # # --seed 1234 124 | # config.order = :random 125 | # 126 | # # Seed global randomization in this process using the `--seed` CLI option. 127 | # # Setting this allows you to use `--seed` to deterministically reproduce 128 | # # test failures related to randomization by passing the same `--seed` value 129 | # # as the one that triggered the failure. 130 | # Kernel.srand config.seed 131 | end 132 | -------------------------------------------------------------------------------- /system/boot/db.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Container.boot(:db) do |container| 4 | init do 5 | use :settings 6 | 7 | register('db.config', ROM::Configuration.new(:sql, container[:settings].database_url)) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /system/boot/logger.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Container.boot(:logger) do |container| 4 | init do 5 | require 'semantic_logger' 6 | require 'stringio' 7 | 8 | use :settings 9 | 10 | if container[:settings].logger_json_formatter == 'true' 11 | SemanticLogger.add_appender(io: logger_io, formatter: :json) 12 | else 13 | SemanticLogger.add_appender(io: logger_io) 14 | end 15 | 16 | SemanticLogger.default_level = container[:settings].logger_level 17 | container.register(:logger, SemanticLogger['Service']) 18 | end 19 | 20 | # detect default logger IO output 21 | def logger_io 22 | ENV['PROJECT_ENV'] == 'test' ? StringIO.new : STDOUT 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /system/boot/persistence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Container.boot(:persistence) do |container| 4 | unless container[:settings].project_apps.include?('rake') 5 | init do 6 | use :db 7 | 8 | config = container['db.config'] 9 | config.auto_registration(container.root + 'lib/persistance') 10 | 11 | register('persistence.container', ROM.container(config)) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /system/boot/settings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry/system/components' 4 | 5 | Container.boot(:settings, from: :system) do 6 | settings do 7 | Types = Core::Types 8 | 9 | key :project_env, Types::String 10 | key :project_apps, Types::ProjectApps 11 | 12 | key :database_url, Types::String.constrained(filled: true) 13 | # key :database_connection_validation_timeout, Types::Coercible::Int.optional # in seconds 14 | 15 | key :logger_json_formatter, Types::String.optional 16 | key :logger_level, Types::LoggerLevel 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /system/container.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'dry/system/container' 4 | require_relative '../lib/core/types' 5 | require_relative '../lib/core/operation' 6 | 7 | require 'rom' 8 | require 'rom-sql' 9 | 10 | # General container class for project dependencies 11 | # 12 | # {http://dry-rb.org/gems/dry-system/ Dry-system documentation} 13 | class Container < Dry::System::Container 14 | # use :bootsnap 15 | use :env 16 | 17 | configure do |config| 18 | ENV['PROJECT_ENV'] ||= 'development' 19 | config.env = ENV['PROJECT_ENV'] 20 | end 21 | 22 | load_paths!('system', 'lib', 'services', 'apps') 23 | end 24 | 25 | Import = Container.injector 26 | 27 | require_relative './registration' unless Container[:settings].project_apps.include?('rake') 28 | -------------------------------------------------------------------------------- /system/registration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Place for auto register different components of the project 4 | class Container 5 | auto_register!('lib') do |config| 6 | config.memoize = true 7 | config.instance(&:instance) 8 | end 9 | 10 | # business logic 11 | auto_register!('services/accounts') do |config| 12 | config.memoize = true 13 | config.instance do |component| 14 | component.identifier[/workers/] ? component.loader.constant : component.instance 15 | end 16 | end 17 | 18 | # transport layer 19 | if self[:settings].project_apps.include?('http') 20 | auto_register!('apps/http') do |config| 21 | config.memoize = true 22 | config.instance(&:instance) 23 | end 24 | end 25 | end 26 | --------------------------------------------------------------------------------