├── .env.development ├── .env.test ├── .gitignore ├── .rspec ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app └── layers │ ├── application │ └── order │ │ ├── commands │ │ ├── add_product_command.rb │ │ ├── change_product_quantity_command.rb │ │ ├── create_order_command.rb │ │ └── remove_product_command.rb │ │ └── order_application.rb │ ├── domain │ └── order │ │ ├── item.rb │ │ ├── order.rb │ │ ├── order_repository.rb │ │ ├── product.rb │ │ └── product_repository.rb │ ├── infra │ ├── jobs │ │ └── application_job.rb │ ├── models │ │ └── application_record.rb │ ├── query_objects │ │ └── orders_query.rb │ └── repositories │ │ ├── order_repository.rb │ │ └── product_repository.rb │ └── web │ ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ └── controllers │ ├── application_controller.rb │ └── orders_controller.rb ├── bin ├── bundle ├── rails ├── rake ├── setup └── update ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── cors.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20180417201502_create_orders.rb │ ├── 20180417202039_create_products.rb │ └── 20180417211238_create_items.rb ├── schema.rb └── seeds.rb ├── lib ├── exceptions │ └── business_exception.rb └── tasks │ └── .keep ├── log └── .keep ├── public └── robots.txt ├── spec ├── rails_helper.rb └── spec_helper.rb ├── tmp └── .keep └── vendor └── .keep /.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_HOST = '127.0.0.1' 2 | DATABASE_PASSWORD = '' 3 | DATABASE_USERNAME = 'root' 4 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | DATABASE_HOST = '127.0.0.1' 2 | DATABASE_PASSWORD = '' 3 | DATABASE_USERNAME = 'root' 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore uploaded files in development 17 | /storage/* 18 | 19 | .byebug_history 20 | 21 | # Ignore master key for decrypting credentials and more. 22 | /config/master.key 23 | 24 | # Ignore intellij files 25 | .idea/ -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --no-profile 4 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | ddd-rails-example 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.0 -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.5.0' 5 | 6 | gem 'dotenv-rails', '~> 2.2', '>= 2.2.2' 7 | gem 'rails', '~> 5.2.0' 8 | gem 'pg', '>= 0.18', '< 2.0' 9 | gem 'puma', '~> 3.11' 10 | gem 'jbuilder', '~> 2.5' 11 | gem 'bcrypt', '~> 3.1.7' 12 | gem 'mini_magick', '~> 4.8' 13 | gem 'bootsnap', '>= 1.1.0', require: false 14 | gem 'rack-cors' 15 | 16 | group :development, :test do 17 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 18 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 19 | end 20 | 21 | group :development do 22 | gem 'listen', '>= 3.0.5', '< 3.2' 23 | gem 'pry', '~> 0.11.3' 24 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 25 | gem 'spring', '~> 2.0', '>= 2.0.2' 26 | gem 'spring-watcher-listen', '~> 2.0.0' 27 | end 28 | 29 | group :test do 30 | gem 'rspec-rails', '~> 3.7', '>= 3.7.2' 31 | end 32 | 33 | 34 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 35 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 36 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.0) 5 | actionpack (= 5.2.0) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.0) 9 | actionpack (= 5.2.0) 10 | actionview (= 5.2.0) 11 | activejob (= 5.2.0) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.0) 15 | actionview (= 5.2.0) 16 | activesupport (= 5.2.0) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.0) 22 | activesupport (= 5.2.0) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.0) 28 | activesupport (= 5.2.0) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.0) 31 | activesupport (= 5.2.0) 32 | activerecord (5.2.0) 33 | activemodel (= 5.2.0) 34 | activesupport (= 5.2.0) 35 | arel (>= 9.0) 36 | activestorage (5.2.0) 37 | actionpack (= 5.2.0) 38 | activerecord (= 5.2.0) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.0) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | arel (9.0.0) 46 | bcrypt (3.1.11) 47 | bootsnap (1.3.0) 48 | msgpack (~> 1.0) 49 | builder (3.2.3) 50 | byebug (10.0.2) 51 | coderay (1.1.2) 52 | concurrent-ruby (1.0.5) 53 | crass (1.0.4) 54 | diff-lcs (1.3) 55 | dotenv (2.2.2) 56 | dotenv-rails (2.2.2) 57 | dotenv (= 2.2.2) 58 | railties (>= 3.2, < 6.0) 59 | erubi (1.7.1) 60 | ffi (1.9.23) 61 | globalid (0.4.1) 62 | activesupport (>= 4.2.0) 63 | i18n (1.0.0) 64 | concurrent-ruby (~> 1.0) 65 | jbuilder (2.7.0) 66 | activesupport (>= 4.2.0) 67 | multi_json (>= 1.2) 68 | listen (3.1.5) 69 | rb-fsevent (~> 0.9, >= 0.9.4) 70 | rb-inotify (~> 0.9, >= 0.9.7) 71 | ruby_dep (~> 1.2) 72 | loofah (2.2.2) 73 | crass (~> 1.0.2) 74 | nokogiri (>= 1.5.9) 75 | mail (2.7.0) 76 | mini_mime (>= 0.1.1) 77 | marcel (0.3.2) 78 | mimemagic (~> 0.3.2) 79 | method_source (0.9.0) 80 | mimemagic (0.3.2) 81 | mini_magick (4.8.0) 82 | mini_mime (1.0.0) 83 | mini_portile2 (2.3.0) 84 | minitest (5.11.3) 85 | msgpack (1.2.4) 86 | multi_json (1.13.1) 87 | nio4r (2.3.0) 88 | nokogiri (1.8.2) 89 | mini_portile2 (~> 2.3.0) 90 | pg (1.0.0) 91 | pry (0.11.3) 92 | coderay (~> 1.1.0) 93 | method_source (~> 0.9.0) 94 | puma (3.11.4) 95 | rack (2.0.4) 96 | rack-cors (1.0.2) 97 | rack-test (1.0.0) 98 | rack (>= 1.0, < 3) 99 | rails (5.2.0) 100 | actioncable (= 5.2.0) 101 | actionmailer (= 5.2.0) 102 | actionpack (= 5.2.0) 103 | actionview (= 5.2.0) 104 | activejob (= 5.2.0) 105 | activemodel (= 5.2.0) 106 | activerecord (= 5.2.0) 107 | activestorage (= 5.2.0) 108 | activesupport (= 5.2.0) 109 | bundler (>= 1.3.0) 110 | railties (= 5.2.0) 111 | sprockets-rails (>= 2.0.0) 112 | rails-dom-testing (2.0.3) 113 | activesupport (>= 4.2.0) 114 | nokogiri (>= 1.6) 115 | rails-html-sanitizer (1.0.4) 116 | loofah (~> 2.2, >= 2.2.2) 117 | railties (5.2.0) 118 | actionpack (= 5.2.0) 119 | activesupport (= 5.2.0) 120 | method_source 121 | rake (>= 0.8.7) 122 | thor (>= 0.18.1, < 2.0) 123 | rake (12.3.1) 124 | rb-fsevent (0.10.3) 125 | rb-inotify (0.9.10) 126 | ffi (>= 0.5.0, < 2) 127 | rspec-core (3.7.1) 128 | rspec-support (~> 3.7.0) 129 | rspec-expectations (3.7.0) 130 | diff-lcs (>= 1.2.0, < 2.0) 131 | rspec-support (~> 3.7.0) 132 | rspec-mocks (3.7.0) 133 | diff-lcs (>= 1.2.0, < 2.0) 134 | rspec-support (~> 3.7.0) 135 | rspec-rails (3.7.2) 136 | actionpack (>= 3.0) 137 | activesupport (>= 3.0) 138 | railties (>= 3.0) 139 | rspec-core (~> 3.7.0) 140 | rspec-expectations (~> 3.7.0) 141 | rspec-mocks (~> 3.7.0) 142 | rspec-support (~> 3.7.0) 143 | rspec-support (3.7.1) 144 | ruby_dep (1.5.0) 145 | spring (2.0.2) 146 | activesupport (>= 4.2) 147 | spring-watcher-listen (2.0.1) 148 | listen (>= 2.7, < 4.0) 149 | spring (>= 1.2, < 3.0) 150 | sprockets (3.7.1) 151 | concurrent-ruby (~> 1.0) 152 | rack (> 1, < 3) 153 | sprockets-rails (3.2.1) 154 | actionpack (>= 4.0) 155 | activesupport (>= 4.0) 156 | sprockets (>= 3.0.0) 157 | thor (0.20.0) 158 | thread_safe (0.3.6) 159 | tzinfo (1.2.5) 160 | thread_safe (~> 0.1) 161 | websocket-driver (0.7.0) 162 | websocket-extensions (>= 0.1.0) 163 | websocket-extensions (0.1.3) 164 | 165 | PLATFORMS 166 | ruby 167 | 168 | DEPENDENCIES 169 | bcrypt (~> 3.1.7) 170 | bootsnap (>= 1.1.0) 171 | byebug 172 | dotenv-rails (~> 2.2, >= 2.2.2) 173 | jbuilder (~> 2.5) 174 | listen (>= 3.0.5, < 3.2) 175 | mini_magick (~> 4.8) 176 | pg (>= 0.18, < 2.0) 177 | pry (~> 0.11.3) 178 | puma (~> 3.11) 179 | rack-cors 180 | rails (~> 5.2.0) 181 | rspec-rails (~> 3.7, >= 3.7.2) 182 | spring (~> 2.0, >= 2.0.2) 183 | spring-watcher-listen (~> 2.0.0) 184 | tzinfo-data 185 | 186 | RUBY VERSION 187 | ruby 2.5.0p0 188 | 189 | BUNDLED WITH 190 | 1.16.1 191 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2018 Murilo Capanema and Fabricio Rissetto 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is this project? 2 | 3 | **DDD Rails Sample** is a open-source project meant to be used as a start point, or an inspiration, for those who want to build Domain Driven Design applications in Ruby. As Rails is the most robust web framework in Ruby community we tried to use it in a way we could modularize the layers and decouple responsabilities. This project is the result of this efforts. 4 | 5 | **NOTE:** This is NOT intended to be a definitive solution or a production ready project 6 | 7 | # Technologies/frameworks involved 8 | 9 | - Ruby on Rails 10 | - Active Record 11 | - Dry::Types 12 | - Dry::Validations 13 | 14 | # Architecture 15 | 16 | - Object oriented programming following SOLID and Clean Code principles 17 | - Domain Driven Design 18 | - Repository Pattern 19 | - CQRS (same database) 20 | - Commands 21 | - Queries 22 | 23 | ### Nice to have in the future 24 | - [ ] Include different Bounded Contexts 25 | - [ ] Domain Events 26 | - [ ] IoC Container or Service Locator 27 | - [ ] Domain Notifications instead of raising exceptions 28 | - [ ] Event Sourcing 29 | - [ ] A simple front-end implementation 30 | 31 | # Setup 32 | 33 | ```bash 34 | bundle install 35 | rails db:create 36 | rails db:migrate 37 | rails db:seed 38 | rails s 39 | ``` 40 | 41 | ## Different implementation versions 42 | 43 | This project describes a two-phase implementation of the Repository pattern. The first implementation (version 1) is somewhat more pragmatic as the domain class itself inherit from Active Record. In the second one (version 2) we evolve this implementation in a way that our domain model became PORO classes, but that came with a cost as you can see in more details below. 44 | 45 | ### Version 1 46 | 47 | In this version we treat te **Persistence Model** and **Domain Model** as the same thing. We use this in the first version because it is a way more simple, fast, and we can have take advantage from all benefits the ORM offers. 48 | 49 | > **ORM + DDD dilemma**: You can see more details about the tradeoffs and the risks of using the Persistence Model and Domain Models as the same thing or in a separated way [in this SO answer](https://stackoverflow.com/a/34436709/890890). 50 | 51 | The only problem with Active Record is that, different from other ORMs like SQLAlchemy (Python), Hibernate (Java), Entity Framework (.NET), *you can't create an external class to map your domain model to the database*. In Active Record your class MUST inherit from `ActiveRecord::Base` in order to be persisted. This breaks (:hankey:) the [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) but as mentioned above, its a tradeoff that you may want to pay in face to the pragmatism. 52 | 53 | 54 | ### Version 2 55 | 56 | ... More details soon ... 57 | 58 | ## About 59 | 60 | The **DDD Rails Sample** was developed by [Murilo Capanema](https://github.com/mcapanema) and [Fabrício Rissetto](https://github.com/fabriciorissetto/) under the WTFPL license. 61 | 62 | Contributions are welcome :heartbeat: 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/layers/application/order/commands/add_product_command.rb: -------------------------------------------------------------------------------- 1 | module Application 2 | module Order 3 | module Commands 4 | class AddProductCommand 5 | attr_accessor :order_id, :product_id, :quantity 6 | 7 | def initialize(order_id:, product_id:, quantity:) 8 | @order_id = order_id 9 | @product_id = product_id 10 | @quantity = quantity 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/layers/application/order/commands/change_product_quantity_command.rb: -------------------------------------------------------------------------------- 1 | module Application 2 | module Order 3 | module Commands 4 | class ChangeProductQuantityCommand 5 | attr_accessor :order_id, :product_id, :quantity 6 | 7 | def initialize(order_id:, product_id:, quantity:) 8 | @order_id = order_id 9 | @product_id = product_id 10 | @quantity = quantity 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/layers/application/order/commands/create_order_command.rb: -------------------------------------------------------------------------------- 1 | module Application 2 | module Order 3 | module Commands 4 | class CreateOrderCommand 5 | attr_accessor :customer 6 | 7 | def initialize(customer:) 8 | @customer = customer 9 | end 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/layers/application/order/commands/remove_product_command.rb: -------------------------------------------------------------------------------- 1 | module Application 2 | module Order 3 | module Commands 4 | class RemoveProductCommand 5 | attr_accessor :order_id, :product_id 6 | 7 | def initialize(order_id:, product_id:) 8 | @order_id = order_id 9 | @product_id = product_id 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/layers/application/order/order_application.rb: -------------------------------------------------------------------------------- 1 | module Application 2 | module Order 3 | class OrderApplication 4 | def initialize(repositories = {}) 5 | @order_repository = repositories.fetch(:order) { Infra::Repositories::OrderRepository.new } 6 | @product_repository = repositories.fetch(:product) { Infra::Repositories::ProductRepository.new } 7 | end 8 | 9 | def create_order(create_order_command) 10 | order = Domain::Order::Order.new(customer: create_order_command.customer) 11 | 12 | ActiveRecord::Base.transaction do 13 | @order_repository.save(order) 14 | 15 | order.id 16 | end 17 | end 18 | 19 | def add_product(add_product_command) 20 | order = @order_repository.find_by_id(add_product_command.order_id) 21 | product = @product_repository.find_by_id(add_product_command.product_id) 22 | 23 | ActiveRecord::Base.transaction do 24 | order.add_product(product, add_product_command.quantity) 25 | @order_repository.save(order) 26 | end 27 | end 28 | 29 | def change_product_quantity(change_product_quantity_command) 30 | order = @order_repository.find_by_id(change_product_quantity_command.order_id) 31 | product = @product_repository.find_by_id(change_product_quantity_command.product_id) 32 | 33 | ActiveRecord::Base.transaction do 34 | order.change_product_quantity(product, change_product_quantity_command.quantity) 35 | @order_repository.save(order) 36 | end 37 | end 38 | 39 | def remove_product(remove_product_command) 40 | order = @order_repository.find_by_id(remove_product_command.order_id) 41 | product = @product_repository.find_by_id(remove_product_command.product_id) 42 | 43 | ActiveRecord::Base.transaction do 44 | order.remove_product(product) 45 | @order_repository.save(order) 46 | end 47 | end 48 | 49 | def find_order_by_id(id) 50 | Infra::QueryObjects::OrdersQuery.order_by_id(id) 51 | end 52 | 53 | def find_last_orders 54 | Infra::QueryObjects::OrdersQuery.last_orders 55 | end 56 | 57 | def find_orders_per_users 58 | Infra::QueryObjects::OrdersQuery.orders_per_users 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/layers/domain/order/item.rb: -------------------------------------------------------------------------------- 1 | module Domain 2 | module Order 3 | class Item < Infra::Models::ApplicationRecord 4 | after_initialize :validate 5 | 6 | belongs_to :product 7 | 8 | attribute :quantity, :integer 9 | 10 | def validate 11 | validate_quantity! 12 | validate_product_presence! 13 | end 14 | 15 | def change_quantity(quantity) 16 | validate_quantity! 17 | self.quantity = quantity 18 | end 19 | 20 | private 21 | 22 | def validate_quantity! 23 | raise Exceptions::BusinessException.new('Product must be greater than zero') unless self.quantity.present? && self.quantity > 0 24 | end 25 | 26 | def validate_product_presence! 27 | raise Exceptions::BusinessException.new('Product must be informed') unless self.product.present? 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/layers/domain/order/order.rb: -------------------------------------------------------------------------------- 1 | module Domain 2 | module Order 3 | class Order < Infra::Models::ApplicationRecord 4 | has_many :items, autosave: true 5 | 6 | attribute :customer, :string # just a string because we are lazy 7 | 8 | def add_product(product, quantity) 9 | raise Exceptions::BusinessException.new('Product already exists') if product_already_exists?(product) 10 | 11 | items << Item.new(quantity: quantity, product: product) 12 | end 13 | 14 | def change_product_quantity(product, quantity) 15 | validate_product_presence!(product) 16 | item = items.find { |item| item.product == product } 17 | item.change_quantity(quantity) 18 | end 19 | 20 | def remove_product(product) 21 | validate_product_presence!(product) 22 | items = self.items.reject { |item| item.product == product } 23 | self.items = items 24 | end 25 | 26 | private 27 | 28 | def product_already_exists?(product) 29 | items.select { |item| item.product == product }.present? 30 | end 31 | 32 | def validate_product_presence!(product) 33 | raise Exceptions::BusinessException.new('Product not found on item list') unless items.find { |item| item.product == product }.present? 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/layers/domain/order/order_repository.rb: -------------------------------------------------------------------------------- 1 | module Domain 2 | module Order 3 | class OrderRepository 4 | def save(order) 5 | raise "Not implemented yet" 6 | end 7 | 8 | def find_by_id(id) 9 | raise "Not implemented yet" 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/layers/domain/order/product.rb: -------------------------------------------------------------------------------- 1 | module Domain 2 | module Order 3 | class Product < Infra::Models::ApplicationRecord 4 | 5 | attribute :description, :string 6 | attribute :value, :decimal 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/layers/domain/order/product_repository.rb: -------------------------------------------------------------------------------- 1 | module Domain 2 | module Order 3 | class ProductRepository 4 | def find_by_id(id) 5 | raise 'Not implemented yet' 6 | end 7 | end 8 | end 9 | end -------------------------------------------------------------------------------- /app/layers/infra/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/layers/infra/models/application_record.rb: -------------------------------------------------------------------------------- 1 | module Infra 2 | module Models 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/layers/infra/query_objects/orders_query.rb: -------------------------------------------------------------------------------- 1 | module Infra 2 | module QueryObjects 3 | class OrdersQuery < Domain::Order::Order 4 | class << self 5 | def order_by_id(id) 6 | self 7 | .includes(:items) 8 | .find_by(id: id) 9 | end 10 | 11 | def last_orders 12 | self 13 | .order('created_at desc') 14 | .limit(10) 15 | end 16 | 17 | def orders_per_users 18 | self 19 | .all 20 | .group(:customer) 21 | .count 22 | .map do |key, value| 23 | { 24 | user: key, 25 | quantity: value 26 | } 27 | end 28 | end 29 | end 30 | end 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /app/layers/infra/repositories/order_repository.rb: -------------------------------------------------------------------------------- 1 | module Infra 2 | module Repositories 3 | class OrderRepository < Domain::Order::OrderRepository 4 | def initialize(model = {}) 5 | @order = model.fetch(:order) { Domain::Order::Order } 6 | end 7 | 8 | def save(order) 9 | order.save 10 | end 11 | 12 | def find_by_id(id) 13 | @order.find_by(id: id) 14 | end 15 | end 16 | end 17 | end 18 | 19 | -------------------------------------------------------------------------------- /app/layers/infra/repositories/product_repository.rb: -------------------------------------------------------------------------------- 1 | module Infra 2 | module Repositories 3 | class ProductRepository < Domain::Order::ProductRepository 4 | def initialize(model = {}) 5 | @product = model.fetch(:product) { Domain::Order::Product } 6 | end 7 | 8 | def find_by_id(id) 9 | @product.find_by(id: id) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/layers/web/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module Web 2 | module Channels 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/layers/web/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module Web 2 | module Channels 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/layers/web/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Web 2 | module Controllers 3 | class ApplicationController < ActionController::API 4 | rescue_from Exception do |e| 5 | # You may want to log more robust information here 6 | Rails.logger.error("Error message: #{e.message}") 7 | 8 | render json: { error: 'Unexpected Error' }, status: :internal_server_error 9 | end 10 | 11 | rescue_from Exceptions::BusinessException do |e| 12 | render json: { error: e.message }, status: :bad_request 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/layers/web/controllers/orders_controller.rb: -------------------------------------------------------------------------------- 1 | module Web 2 | module Controllers 3 | class OrdersController < ApplicationController 4 | def create_order 5 | command = Application::Order::Commands::CreateOrderCommand.new(customer: params.dig(:customer)) 6 | order_id = Application::Order::OrderApplication.new.create_order(command) 7 | 8 | head :created, location: "/orders/#{order_id}" 9 | end 10 | 11 | def add_product 12 | command = Application::Order::Commands::AddProductCommand.new( 13 | product_id: params.dig(:product_id), 14 | order_id: params.dig(:order_id), 15 | quantity: params.dig(:quantity) 16 | ) 17 | Application::Order::OrderApplication.new.add_product(command) 18 | 19 | head :ok 20 | end 21 | 22 | def change_product_quantity 23 | command = Application::Order::Commands::ChangeProductQuantityCommand.new( 24 | product_id: params.dig(:product_id), 25 | order_id: params.dig(:order_id), 26 | quantity: params.dig(:quantity) 27 | ) 28 | Application::Order::OrderApplication.new.change_product_quantity(command) 29 | 30 | head :ok 31 | end 32 | 33 | def remove_product 34 | command = Application::Order::Commands::RemoveProductCommand.new( 35 | product_id: params.dig(:product_id), 36 | order_id: params.dig(:order_id) 37 | ) 38 | Application::Order::OrderApplication.new.remove_product(command) 39 | 40 | head :ok 41 | end 42 | 43 | def find_order_by_id 44 | order = Application::Order::OrderApplication.new.find_order_by_id(params.dig(:order_id)) 45 | 46 | render json: order, include: :items, status: :ok 47 | end 48 | 49 | def find_last_orders 50 | orders = Application::Order::OrderApplication.new.find_last_orders 51 | 52 | render json: { orders: orders }, status: :ok 53 | end 54 | 55 | def find_orders_per_users 56 | orders = Application::Order::OrderApplication.new.find_orders_per_users 57 | 58 | render json: { orders: orders }, status: :ok 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?('config/database.yml') 22 | # cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! 'bin/rails db:setup' 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! 'bin/rails log:clear tmp:clear' 30 | 31 | puts "\n== Restarting application server ==" 32 | system! 'bin/rails restart' 33 | end 34 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Updating database ==" 21 | system! 'bin/rails db:migrate' 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system! 'bin/rails log:clear tmp:clear' 25 | 26 | puts "\n== Restarting application server ==" 27 | system! 'bin/rails restart' 28 | end 29 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | require "action_view/railtie" 12 | require "action_cable/engine" 13 | # require "sprockets/railtie" 14 | # require "rails/test_unit/railtie" 15 | 16 | # Require the gems listed in Gemfile, including any gems 17 | # you've limited to :test, :development, or :production. 18 | Bundler.require(*Rails.groups) 19 | 20 | module RailsRepository 21 | class Application < Rails::Application 22 | # Initialize configuration defaults for originally generated Rails version. 23 | config.load_defaults 5.2 24 | 25 | # Settings in config/environments/* take precedence over those specified here. 26 | # Application configuration can go into files in config/initializers 27 | # -- all .rb files in that directory are automatically loaded after loading 28 | # the framework and any gems in your application. 29 | 30 | # Only loads a smaller set of middleware suitable for API only apps. 31 | # Middleware like session, flash, cookies can be added back manually. 32 | # Skip views, helpers and assets when generating a new resource. 33 | config.api_only = true 34 | 35 | config.autoload_paths << Rails.root.join("lib") 36 | config.eager_load_paths << Rails.root.join("lib") 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails-repository_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | VfoxDAQVu31Qt2Y7BOzSMA5pq4+cL6NwvZjQ2Meml8glp8QQRzAZOVvqiFMSodVwI9/4TCFRzwgqsptKR3RE91UN7sYDWfajKpRGVohIwEVD3qyxR4xNbq/xTC6xxTsXAIyEBi5N4hG/XSQVOy2DOLHW76G7ryWFFo7T2QNkuxRqadeOiKsmvseITP1sgb7Ga+4sGi/UI+r4dljfMlU+O9WRH9jhUM7JX5Kd1Cd5WGqLDppUhRubua1mrT3abChIC3mfEITeKcYs0A5Q7cEQTwL/TLAFjdxHyuVb19fplXxSNWjO8am/1FE9hXM/XU75FilnNOT6+5nYWVOSw62Jl6YrUJ94fE6zgHoWWLUq805TD/yo8TbWzFX2xDxL0zb38Z01vwZUZBfy4pwAfuKPj0bTkw/o6Sv1NqOD--3so9uC4KOmfQlXtU--BAPmMt0QvBO8uhXs2Sn8wA== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 5 | username: <%= ENV['DATABASE_USERNAME'] %> 6 | password: <%= ENV['DATABASE_PASSWORD'] %> 7 | host: <%= ENV['DATABASE_HOST'] %> 8 | 9 | development: 10 | <<: *default 11 | database: ddd_rails_example_development 12 | 13 | test: 14 | <<: *default 15 | database: ddd_rails_example_test 16 | 17 | production: 18 | <<: *default 19 | database: ddd_rails_example_production 20 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.action_controller.perform_caching = true 19 | 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options) 31 | config.active_storage.service = :local 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 24 | 25 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 26 | # config.action_controller.asset_host = 'http://assets.example.com' 27 | 28 | # Specifies the header that your server uses for sending files. 29 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 30 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 31 | 32 | # Store uploaded files on the local file system (see config/storage.yml for options) 33 | config.active_storage.service = :local 34 | 35 | # Mount Action Cable outside main process or domain 36 | # config.action_cable.mount_path = nil 37 | # config.action_cable.url = 'wss://example.com/cable' 38 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 39 | 40 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 41 | # config.force_ssl = true 42 | 43 | # Use the lowest log level to ensure availability of diagnostic information 44 | # when problems arise. 45 | config.log_level = :debug 46 | 47 | # Prepend all log lines with the following tags. 48 | config.log_tags = [ :request_id ] 49 | 50 | # Use a different cache store in production. 51 | # config.cache_store = :mem_cache_store 52 | 53 | # Use a real queuing backend for Active Job (and separate queues per environment) 54 | # config.active_job.queue_adapter = :resque 55 | # config.active_job.queue_name_prefix = "rails-repository_#{Rails.env}" 56 | 57 | config.action_mailer.perform_caching = false 58 | 59 | # Ignore bad email addresses and do not raise email delivery errors. 60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 61 | # config.action_mailer.raise_delivery_errors = false 62 | 63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 64 | # the I18n.default_locale when a translation cannot be found). 65 | config.i18n.fallbacks = true 66 | 67 | # Send deprecation notices to registered listeners. 68 | config.active_support.deprecation = :notify 69 | 70 | # Use default logging formatter so that PID and timestamp are not suppressed. 71 | config.log_formatter = ::Logger::Formatter.new 72 | 73 | # Use a different logger for distributed setups. 74 | # require 'syslog/logger' 75 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 76 | 77 | if ENV["RAILS_LOG_TO_STDOUT"].present? 78 | logger = ActiveSupport::Logger.new(STDOUT) 79 | logger.formatter = config.log_formatter 80 | config.logger = ActiveSupport::TaggedLogging.new(logger) 81 | end 82 | 83 | # Do not dump schema after migrations. 84 | config.active_record.dump_schema_after_migration = false 85 | end 86 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory 32 | config.active_storage.service = :test 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Tell Action Mailer not to deliver emails to the real world. 37 | # The :test delivery method accumulates sent emails in the 38 | # ActionMailer::Base.deliveries array. 39 | config.action_mailer.delivery_method = :test 40 | 41 | # Print deprecation notices to the stderr. 42 | config.active_support.deprecation = :stderr 43 | 44 | # Raises error for missing translations 45 | # config.action_view.raise_on_missing_translations = true 46 | end 47 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins 'example.com' 11 | # 12 | # resource '*', 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | scope module: 'web' do 3 | scope module: 'controllers' do 4 | # Commands 5 | post 'orders', to: 'orders#create_order' 6 | patch 'orders/:order_id/products', to: 'orders#add_product' 7 | patch 'orders/:order_id/products/:product_id', to: 'orders#change_product_quantity' 8 | delete 'orders/:order_id/products/:product_id', to: 'orders#remove_product' 9 | 10 | # Queries 11 | get 'orders/:order_id', to: 'orders#find_order_by_id' 12 | get 'orders', to: 'orders#find_last_orders', 13 | :constraints => lambda { |request| request.params.key?(:last_orders) } 14 | get 'orders', to: 'orders#find_orders_per_users', 15 | :constraints => lambda { |request| request.params.key?(:orders_per_users) } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20180417201502_create_orders.rb: -------------------------------------------------------------------------------- 1 | class CreateOrders < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :orders do |t| 4 | t.timestamps 5 | t.string :customer 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20180417202039_create_products.rb: -------------------------------------------------------------------------------- 1 | class CreateProducts < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :products do |t| 4 | t.string :description 5 | t.decimal :value 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20180417211238_create_items.rb: -------------------------------------------------------------------------------- 1 | class CreateItems < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :items do |t| 4 | t.references :order, foreign_key: true 5 | t.references :product, foreign_key: true 6 | t.integer :quantity 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2018_04_17_211238) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "plpgsql" 17 | 18 | create_table "items", force: :cascade do |t| 19 | t.bigint "order_id" 20 | t.bigint "product_id" 21 | t.integer "quantity" 22 | t.datetime "created_at", null: false 23 | t.datetime "updated_at", null: false 24 | t.index ["order_id"], name: "index_items_on_order_id" 25 | t.index ["product_id"], name: "index_items_on_product_id" 26 | end 27 | 28 | create_table "orders", force: :cascade do |t| 29 | t.datetime "created_at", null: false 30 | t.datetime "updated_at", null: false 31 | t.string "customer" 32 | end 33 | 34 | create_table "products", force: :cascade do |t| 35 | t.string "description" 36 | t.decimal "value" 37 | t.datetime "created_at", null: false 38 | t.datetime "updated_at", null: false 39 | end 40 | 41 | add_foreign_key "items", "orders" 42 | add_foreign_key "items", "products" 43 | end 44 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # Create Products 2 | product1 = Domain::Order::Product.new(description: 'Keyboard', value: 15.50) 3 | product1.save 4 | 5 | product2 = Domain::Order::Product.new(description: 'Mouse', value: 5.00) 6 | product2.save 7 | 8 | product3 = Domain::Order::Product.new(description: 'Monitor', value: 99.90) 9 | product3.save 10 | 11 | # Create Orders 12 | order = Domain::Order::Order.new(customer: 'Jonny') 13 | order.add_product(product1, 1) 14 | order.add_product(product2, 1) 15 | order.save(order) 16 | 17 | order = Domain::Order::Order.new(customer: 'Jonny') 18 | order.add_product(product3, 1) 19 | order.save(order) 20 | 21 | order = Domain::Order::Order.new(customer: 'Anna') 22 | order.add_product(product1, 2) 23 | order.save(order) 24 | 25 | order = Domain::Order::Order.new(customer: 'Matt') 26 | order.add_product(product1, 5) 27 | order.save(order) 28 | 29 | order = Domain::Order::Order.new(customer: 'Bob') 30 | order.add_product(product2, 2) 31 | order_repository.save(order) 32 | 33 | order = Domain::Order::Order.new(customer: 'Alice') 34 | order.add_product(product2, 10) 35 | order.save(order) 36 | -------------------------------------------------------------------------------- /lib/exceptions/business_exception.rb: -------------------------------------------------------------------------------- 1 | # Exception used to express errors derived from business logic 2 | # Its message is meant to be "safe" to be shown to the requester (REST calls, etc) 3 | module Exceptions 4 | class BusinessException < StandardError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Creditas/ddd-rails-sample/bb3306672ced2903a2c8e86490151a4fb18018bf/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Creditas/ddd-rails-sample/bb3306672ced2903a2c8e86490151a4fb18018bf/log/.keep -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove this line. 27 | ActiveRecord::Migration.maintain_test_schema! 28 | 29 | RSpec.configure do |config| 30 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 31 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 32 | 33 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 34 | # examples within a transaction, remove the following line or assign false 35 | # instead of true. 36 | config.use_transactional_fixtures = true 37 | 38 | # RSpec Rails can automatically mix in different behaviours to your tests 39 | # based on their file location, for example enabling you to call `get` and 40 | # `post` in specs under `spec/controllers`. 41 | # 42 | # You can disable this behaviour by removing the line below, and instead 43 | # explicitly tag your specs with their type, e.g.: 44 | # 45 | # RSpec.describe UsersController, :type => :controller do 46 | # # ... 47 | # end 48 | # 49 | # The different available types are documented in the features, such as in 50 | # https://relishapp.com/rspec/rspec-rails/docs 51 | config.infer_spec_type_from_file_location! 52 | 53 | # Filter lines from Rails gems in backtraces. 54 | config.filter_rails_from_backtrace! 55 | # arbitrary gems may also be filtered via: 56 | # config.filter_gems_from_backtrace("gem name") 57 | end 58 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | =begin 50 | # This allows you to limit a spec run to individual examples or groups 51 | # you care about by tagging them with `:focus` metadata. When nothing 52 | # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | config.filter_run_when_matching :focus 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Creditas/ddd-rails-sample/bb3306672ced2903a2c8e86490151a4fb18018bf/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Creditas/ddd-rails-sample/bb3306672ced2903a2c8e86490151a4fb18018bf/vendor/.keep --------------------------------------------------------------------------------