diff --git a/ecommerce/configuration.rb b/ecommerce/configuration.rb index 9179bc8c9..d210e18e3 100644 --- a/ecommerce/configuration.rb +++ b/ecommerce/configuration.rb @@ -12,10 +12,12 @@ module Ecommerce class Configuration - def initialize(number_generator: nil, payment_gateway: nil, available_vat_rates: []) + def initialize(number_generator: nil, payment_gateway: nil, available_vat_rates: [], event_store:, command_bus:) @number_generator = number_generator @payment_gateway = payment_gateway @available_vat_rates = available_vat_rates + @event_store = event_store + @command_bus = command_bus end def call(event_store, command_bus) @@ -24,9 +26,6 @@ def call(event_store, command_bus) end def configure_bounded_contexts - event_store = Rails.configuration.event_store - command_bus = Rails.configuration.command_bus - raise ArgumentError.new( "Neither number_generator nor payment_gateway can be null" ) if @number_generator.nil? || @payment_gateway.nil? @@ -41,11 +40,11 @@ def configure_bounded_contexts Pricing::Configuration.new, Taxes::Configuration.new(@available_vat_rates), ProductCatalog::Configuration.new, - ].each { |c| c.call(event_store, command_bus) } + ].each { |c| c.call(@event_store, @command_bus) } end def configure_processes(event_store, command_bus) - Processes::Configuration.new.call(event_store, command_bus) + Processes::Configuration.new.call(@event_store, @command_bus) end end end diff --git a/hanami_application/.bundle/config b/hanami_application/.bundle/config new file mode 100644 index 000000000..942410fc9 --- /dev/null +++ b/hanami_application/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_JOBS: "8" diff --git a/hanami_application/.env.development b/hanami_application/.env.development new file mode 100644 index 000000000..af61f1c74 --- /dev/null +++ b/hanami_application/.env.development @@ -0,0 +1 @@ +DATABASE_URL=postgresql://localhost:5432/ecommerce_hanami_dev diff --git a/hanami_application/.env.test b/hanami_application/.env.test new file mode 100644 index 000000000..ebb868824 --- /dev/null +++ b/hanami_application/.env.test @@ -0,0 +1 @@ +DATABASE_URL=postgresql://localhost:5432/ecommerce_hanami_test diff --git a/hanami_application/.gitignore b/hanami_application/.gitignore new file mode 100644 index 000000000..f1112aba5 --- /dev/null +++ b/hanami_application/.gitignore @@ -0,0 +1,2 @@ +.env +log/* diff --git a/hanami_application/.rspec b/hanami_application/.rspec new file mode 100644 index 000000000..c99d2e739 --- /dev/null +++ b/hanami_application/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/hanami_application/Gemfile b/hanami_application/Gemfile new file mode 100644 index 000000000..674df2575 --- /dev/null +++ b/hanami_application/Gemfile @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "hanami", "~> 2.0" +gem "hanami-router", "~> 2.0" +gem "hanami-controller", "~> 2.0" +gem "hanami-validations", "~> 2.0" +gem "hanami-view", "~> 2.0" + +gem "dry-types", "~> 1.0", ">= 1.6.1" +gem "puma" +gem "rake" + +gem "rom" +gem "rom-sql" +gem "pg" + +gem "ruby_event_store" +gem "ruby_event_store-rom", require: "ruby_event_store/rom/sql" +gem "arkency-command_bus" + +gem "infra", path: "../infra" + +group :development, :test do + gem "dotenv" + gem "pry" + gem "pry-byebug" +end + +group :cli, :development do + gem "hanami-reloader" +end + +group :cli, :development, :test do + gem "hanami-rspec" +end + +group :development do + gem "guard-puma", "~> 0.8" +end + +group :test do + gem "rack-test" +end diff --git a/hanami_application/Gemfile.lock b/hanami_application/Gemfile.lock new file mode 100644 index 000000000..7981da872 --- /dev/null +++ b/hanami_application/Gemfile.lock @@ -0,0 +1,304 @@ +PATH + remote: ../infra + specs: + infra (1.0.0) + aggregate_root (~> 2.9.0) + arkency-command_bus + dry-struct + dry-types + rake + ruby_event_store (~> 2.9.0) + ruby_event_store-transformations + sidekiq + +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.1.0) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + aggregate_root (2.9.1) + ruby_event_store (= 2.9.1) + arkency-command_bus (0.4.1) + concurrent-ruby + base64 (0.1.1) + bigdecimal (3.1.4) + byebug (11.1.3) + coderay (1.1.3) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + diff-lcs (1.5.0) + dotenv (2.8.1) + drb (2.1.1) + ruby2_keywords + dry-auto_inject (1.0.1) + dry-core (~> 1.0) + zeitwerk (~> 2.6) + dry-cli (1.0.0) + dry-configurable (1.1.0) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-container (0.11.0) + concurrent-ruby (~> 1.0) + dry-core (1.0.1) + concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) + dry-events (1.0.1) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + dry-files (1.0.2) + dry-inflector (1.0.0) + dry-initializer (3.1.1) + dry-logger (1.0.3) + dry-logic (1.5.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + zeitwerk (~> 2.6) + dry-monitor (1.0.1) + dry-configurable (~> 1.0, < 2) + dry-core (~> 1.0, < 2) + dry-events (~> 1.0, < 2) + dry-schema (1.13.3) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0, >= 1.0.1) + dry-core (~> 1.0, < 2) + dry-initializer (~> 3.0) + dry-logic (>= 1.4, < 2) + dry-types (>= 1.7, < 2) + zeitwerk (~> 2.6) + dry-struct (1.6.0) + dry-core (~> 1.0, < 2) + dry-types (>= 1.7, < 2) + ice_nine (~> 0.11) + zeitwerk (~> 2.6) + dry-system (1.0.1) + dry-auto_inject (~> 1.0, < 2) + dry-configurable (~> 1.0, < 2) + dry-core (~> 1.0, < 2) + dry-inflector (~> 1.0, < 2) + dry-transformer (1.0.1) + zeitwerk (~> 2.6) + dry-types (1.7.1) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-logic (~> 1.4) + zeitwerk (~> 2.6) + dry-validation (1.10.0) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + dry-initializer (~> 3.0) + dry-schema (>= 1.12, < 2) + zeitwerk (~> 2.6) + ffi (1.16.3) + formatador (1.1.0) + guard (2.18.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-puma (0.8.1) + guard (~> 2.14) + guard-compat (~> 1.2) + puma (>= 4.0, < 7) + hanami (2.1.0.beta2.1) + bundler (>= 1.16, < 3) + dry-configurable (~> 1.0, < 2) + dry-core (~> 1.0, < 2) + dry-inflector (~> 1.0, < 2) + dry-logger (~> 1.0, < 2) + dry-monitor (~> 1.0, >= 1.0.1, < 2) + dry-system (~> 1.0, < 2) + hanami-cli (~> 2.1.beta) + hanami-utils (~> 2.1.beta) + zeitwerk (~> 2.6) + hanami-cli (2.1.0.beta2) + bundler (~> 2.1) + dry-cli (~> 1.0, < 2) + dry-files (~> 1.0, >= 1.0.2, < 2) + dry-inflector (~> 1.0, < 2) + rake (~> 13.0) + zeitwerk (~> 2.6) + hanami-controller (2.0.2) + dry-configurable (~> 1.0, < 2) + dry-core (~> 1.0) + hanami-utils (~> 2.0) + rack (~> 2.0) + zeitwerk (~> 2.6) + hanami-reloader (2.0.2) + hanami-cli (~> 2.0) + zeitwerk (~> 2.6) + hanami-router (2.0.2) + mustermann (~> 3.0) + mustermann-contrib (~> 3.0) + rack (~> 2.0) + hanami-rspec (2.0.1) + hanami-cli (~> 2.0) + rake (~> 13.0) + rspec (~> 3.12) + zeitwerk (~> 2.6) + hanami-utils (2.1.0.beta1) + concurrent-ruby (~> 1.0) + dry-core (~> 1.0, < 2) + dry-transformer (~> 1.0, < 2) + hanami-validations (2.0.1) + dry-validation (>= 1.10, < 2) + zeitwerk (~> 2.6.0) + hanami-view (2.1.0.beta2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0, < 2) + temple (~> 0.10.0, >= 0.10.2) + tilt (~> 2.0, >= 2.0.6) + zeitwerk (~> 2.6) + hansi (0.2.1) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + ice_nine (0.11.2) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + lumberjack (1.2.9) + method_source (1.0.0) + minitest (5.20.0) + mustermann (3.0.0) + ruby2_keywords (~> 0.0.1) + mustermann-contrib (3.0.0) + hansi (~> 0.2.0) + mustermann (= 3.0.0) + mutex_m (0.1.2) + nenv (0.3.0) + nio4r (2.5.9) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + pg (1.5.4) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.10.1) + byebug (~> 11.0) + pry (>= 0.13, < 0.15) + puma (6.4.0) + nio4r (~> 2.0) + rack (2.2.8) + rack-test (2.1.0) + rack (>= 1.3) + rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + redis-client (0.17.0) + connection_pool + rom (5.3.0) + rom-changeset (~> 5.3, >= 5.3.0) + rom-core (~> 5.3, >= 5.3.0) + rom-repository (~> 5.3, >= 5.3.0) + rom-changeset (5.3.0) + dry-core (~> 1.0) + rom-core (~> 5.3) + transproc (~> 1.0, >= 1.1.0) + rom-core (5.3.0) + concurrent-ruby (~> 1.1) + dry-configurable (~> 1.0) + dry-core (~> 1.0) + dry-inflector (~> 1.0) + dry-initializer (~> 3.0, >= 3.0.1) + dry-struct (~> 1.0) + dry-types (~> 1.6) + transproc (~> 1.0, >= 1.1.0) + rom-repository (5.3.0) + dry-core (~> 1.0) + dry-initializer (~> 3.0, >= 3.0.1) + rom-core (~> 5.3, >= 5.3.0) + rom-sql (3.6.1) + dry-core (~> 1.0) + dry-types (~> 1.0) + rom (~> 5.2, >= 5.2.1) + sequel (>= 4.49) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + ruby2_keywords (0.0.5) + ruby_event_store (2.9.1) + concurrent-ruby (~> 1.0, >= 1.1.6) + ruby_event_store-rom (2.2.0) + dry-container (>= 0.6) + dry-initializer (>= 3.0) + dry-types (>= 1.0) + rom-changeset (>= 5.0) + rom-repository (>= 5.0) + rom-sql (>= 3.0) + ruby_event_store (>= 2.0.0, < 3.0.0) + sequel (>= 5.11.0) + ruby_event_store-transformations (0.1.0) + activesupport (>= 5.0) + ruby_event_store (>= 2.0.0, < 3.0.0) + sequel (5.73.0) + bigdecimal + shellany (0.0.1) + sidekiq (7.1.5) + concurrent-ruby (< 2) + connection_pool (>= 2.3.0) + rack (>= 2.2.4) + redis-client (>= 0.14.0) + temple (0.10.3) + thor (1.2.2) + tilt (2.3.0) + transproc (1.1.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + zeitwerk (2.6.12) + +PLATFORMS + arm64-darwin-20 + +DEPENDENCIES + arkency-command_bus + dotenv + dry-types (~> 1.0, >= 1.6.1) + guard-puma (~> 0.8) + hanami (~> 2.0) + hanami-controller (~> 2.0) + hanami-reloader + hanami-router (~> 2.0) + hanami-rspec + hanami-validations (~> 2.0) + hanami-view (~> 2.0) + infra! + pg + pry + pry-byebug + puma + rack-test + rake + rom + rom-sql + ruby_event_store + ruby_event_store-rom + +BUNDLED WITH + 2.4.20 diff --git a/hanami_application/Guardfile b/hanami_application/Guardfile new file mode 100644 index 000000000..7c4504a63 --- /dev/null +++ b/hanami_application/Guardfile @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +group :server do + guard "puma", port: ENV.fetch("HANAMI_PORT", 2300) do + watch(%r{config/*}) + watch(%r{lib/*}) + watch(%r{app/*}) + watch(%r{slices/*}) + end +end diff --git a/hanami_application/README.md b/hanami_application/README.md index 1c13d671a..f77470fdb 100644 --- a/hanami_application/README.md +++ b/hanami_application/README.md @@ -1,4 +1 @@ -Coming soon, there's no obstacle to use it with ~~Rails~~ **RubyEventStore**. - -It will reimplement most, if not all, of counterpart Rails application. It will also reuse contexts from [ecommerce/](../ecommerce/) - +# Ecommerce diff --git a/hanami_application/Rakefile b/hanami_application/Rakefile new file mode 100644 index 000000000..4a7fd899a --- /dev/null +++ b/hanami_application/Rakefile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "hanami/rake_tasks" +require "hanami/prepare" +require "ruby_event_store/rom/rake_task" +require "rom/sql/rake_task" + +namespace :db do + task :setup do + Ecommerce::App.prepare :persistence + config = Ecommerce::Container['persistence.config'] + ROM::SQL::RakeSupport.env = ROM.container(config) + end +end diff --git a/hanami_application/app/action.rb b/hanami_application/app/action.rb new file mode 100644 index 000000000..11542af93 --- /dev/null +++ b/hanami_application/app/action.rb @@ -0,0 +1,9 @@ +# auto_register: false +# frozen_string_literal: true + +require "hanami/action" + +module Ecommerce + class Action < Hanami::Action + end +end diff --git a/hanami_application/app/actions/.keep b/hanami_application/app/actions/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/hanami_application/app/actions/orders/create.rb b/hanami_application/app/actions/orders/create.rb new file mode 100644 index 000000000..f96963f5f --- /dev/null +++ b/hanami_application/app/actions/orders/create.rb @@ -0,0 +1,32 @@ +module Ecommerce + module Actions + module Orders + class Create < Ecommerce::Action + include Deps[ + "command_bus", + "repositories.orders", + "persistence.transaction", + ] + + def handle(request, response) + submit_order(request.params[:order_id], request.params[:customer_id]) + + response.format = :json + response.body = orders.by_id(request.params[:order_id]) + .attributes + .slice(:id) + .to_json + end + + private + + def submit_order(order_id, customer_id) + transaction.call do + command_bus.(Ordering::SubmitOrder.new(order_id: order_id)) + command_bus.(Crm::AssignCustomerToOrder.new(order_id: order_id, customer_id: customer_id)) + end + end + end + end + end +end diff --git a/hanami_application/app/actions/orders/index.rb b/hanami_application/app/actions/orders/index.rb new file mode 100644 index 000000000..6506025da --- /dev/null +++ b/hanami_application/app/actions/orders/index.rb @@ -0,0 +1,11 @@ +module Ecommerce + module Actions + module Orders + class Index < Ecommerce::Action + def handle(request, response) + response.render(view) + end + end + end + end +end diff --git a/hanami_application/app/event_handlers/orders/submit.rb b/hanami_application/app/event_handlers/orders/submit.rb new file mode 100644 index 000000000..ef79a84a4 --- /dev/null +++ b/hanami_application/app/event_handlers/orders/submit.rb @@ -0,0 +1,18 @@ +module Ecommerce + module EventHandlers + module Orders + class Submit + include Deps[ + "repositories.orders", + ] + + def call(event) + orders.create( + id: event.data[:order_id], + number: event.data[:order_number] + ) + end + end + end + end +end diff --git a/hanami_application/app/persistence/relations/orders.rb b/hanami_application/app/persistence/relations/orders.rb new file mode 100644 index 000000000..9c4f99b11 --- /dev/null +++ b/hanami_application/app/persistence/relations/orders.rb @@ -0,0 +1,10 @@ +module Ecommerce + module Persistence + module Relations + class Orders < ROM::Relation[:sql] + schema(:orders, infer: true) do + end + end + end + end +end diff --git a/hanami_application/app/repositories/orders.rb b/hanami_application/app/repositories/orders.rb new file mode 100644 index 000000000..b393f2cf7 --- /dev/null +++ b/hanami_application/app/repositories/orders.rb @@ -0,0 +1,17 @@ +module Ecommerce + module Repositories + class Orders < ROM::Repository[:orders] + include Deps[container: "persistence.rom"] + + commands :create + + def by_id(id) + orders.by_pk(id).one + end + + def all + orders.to_a + end + end + end +end diff --git a/hanami_application/app/templates/layouts/app.html.erb b/hanami_application/app/templates/layouts/app.html.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/hanami_application/app/templates/layouts/app.html.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/hanami_application/app/templates/orders/index.html.erb b/hanami_application/app/templates/orders/index.html.erb new file mode 100644 index 000000000..593107c95 --- /dev/null +++ b/hanami_application/app/templates/orders/index.html.erb @@ -0,0 +1,5 @@ +

All Orders

+ +<% orders.each do |order| %> +

Order ID: <%= order.id %>

+<% end %> diff --git a/hanami_application/app/view.rb b/hanami_application/app/view.rb new file mode 100644 index 000000000..3956b64bd --- /dev/null +++ b/hanami_application/app/view.rb @@ -0,0 +1,9 @@ +# auto_register: false +# frozen_string_literal: true + +require "hanami/view" + +module Ecommerce + class View < Hanami::View + end +end diff --git a/hanami_application/app/views/orders/index.rb b/hanami_application/app/views/orders/index.rb new file mode 100644 index 000000000..30a600fa2 --- /dev/null +++ b/hanami_application/app/views/orders/index.rb @@ -0,0 +1,15 @@ +module Ecommerce + module Views + module Orders + class Index < Ecommerce::View + include Deps[ + "repositories.orders" + ] + + expose :orders do + orders.all + end + end + end + end +end diff --git a/hanami_application/config.ru b/hanami_application/config.ru new file mode 100644 index 000000000..879c08584 --- /dev/null +++ b/hanami_application/config.ru @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "hanami/boot" + +run Hanami.app diff --git a/hanami_application/config/app.rb b/hanami_application/config/app.rb new file mode 100644 index 000000000..64e23d98c --- /dev/null +++ b/hanami_application/config/app.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "hanami" + +module Ecommerce + class App < Hanami::App + end +end diff --git a/hanami_application/config/providers/command_bus.rb b/hanami_application/config/providers/command_bus.rb new file mode 100644 index 000000000..a13cbb450 --- /dev/null +++ b/hanami_application/config/providers/command_bus.rb @@ -0,0 +1,8 @@ + +Hanami.app.register_provider :command_bus do + prepare do + require "arkency/command_bus" + + register "command_bus", Arkency::CommandBus.new + end +end diff --git a/hanami_application/config/providers/ecommerce.rb b/hanami_application/config/providers/ecommerce.rb new file mode 100644 index 000000000..c6c93b0f3 --- /dev/null +++ b/hanami_application/config/providers/ecommerce.rb @@ -0,0 +1,25 @@ +Hanami.app.register_provider :ecommerce, namespace: true do + prepare do + require_relative "../../../ecommerce/configuration" + require_relative "../../../ecommerce/ordering/lib/ordering" + + event_store = target["event_store.client"] + command_bus = target["command_bus"] + + number_generator = -> { Ordering::NumberGenerator.new } + payment_gateway = -> { Payments::FakeGateway.new } + + config = Ecommerce::Configuration.new( + event_store: event_store, + command_bus: command_bus, + number_generator: number_generator, + payment_gateway: payment_gateway, + available_vat_rates: [ + Infra::Types::VatRate.new(code: "10", rate: 10), + Infra::Types::VatRate.new(code: "20", rate: 20) + ] + ).call(event_store, command_bus) + + register "config", config + end +end diff --git a/hanami_application/config/providers/event_store.rb b/hanami_application/config/providers/event_store.rb new file mode 100644 index 000000000..e75225f08 --- /dev/null +++ b/hanami_application/config/providers/event_store.rb @@ -0,0 +1,27 @@ +Hanami.app.register_provider :event_store, namespace: true do + prepare do + require "ruby_event_store" + require 'ruby_event_store/rom' + + rom_config = target["persistence.config"] + rom_config.register_mapper RubyEventStore::ROM::Mappers::StreamEntryToSerializedRecord + rom_config.register_mapper RubyEventStore::ROM::Mappers::EventToSerializedRecord + + rom_config.register_relation RubyEventStore::ROM::Relations::Events + rom_config.register_relation RubyEventStore::ROM::Relations::StreamEntries + + repository = RubyEventStore::ROM::EventRepository.new( + rom: ROM.container(rom_config), + serializer: JSON + ) + + client = RubyEventStore::Client.new(repository: repository) + + register "repository", repository + register "client", client + + client.subscribe( + target["event_handlers.orders.submit"], to: [Ordering::OrderSubmitted] + ) + end +end diff --git a/hanami_application/config/providers/persistence.rb b/hanami_application/config/providers/persistence.rb new file mode 100644 index 000000000..26c9fad82 --- /dev/null +++ b/hanami_application/config/providers/persistence.rb @@ -0,0 +1,23 @@ +Hanami.app.register_provider :persistence, namespace: true do + prepare do + require 'rom-changeset' + require 'rom/core' + require 'rom/sql' + require 'rom-repository' + + config = + ROM::Configuration.new( + :sql, target['settings'].database_url + ) + + config.auto_registration( + target.root.join('app/persistence'), + namespace: 'Ecommerce::Persistence::Relations' + ) + + register 'config', config + register 'db', config.gateways[:default].connection + register 'rom', ROM.container(config) + register 'transaction', Infra::Transaction.new(container: config) + end +end diff --git a/hanami_application/config/puma.rb b/hanami_application/config/puma.rb new file mode 100644 index 000000000..814785f02 --- /dev/null +++ b/hanami_application/config/puma.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +max_threads_count = ENV.fetch("HANAMI_MAX_THREADS", 5) +min_threads_count = ENV.fetch("HANAMI_MIN_THREADS") { max_threads_count } +threads min_threads_count, max_threads_count + +port ENV.fetch("HANAMI_PORT", 2300) +environment ENV.fetch("HANAMI_ENV", "development") +workers ENV.fetch("HANAMI_WEB_CONCURRENCY", 2) + +on_worker_boot do + Hanami.shutdown +end + +preload_app! diff --git a/hanami_application/config/routes.rb b/hanami_application/config/routes.rb new file mode 100644 index 000000000..0a37891a0 --- /dev/null +++ b/hanami_application/config/routes.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Ecommerce + class Routes < Hanami::Routes + post "/orders", to: "orders.create" + get "/", to: "orders.index" + end +end diff --git a/hanami_application/config/settings.rb b/hanami_application/config/settings.rb new file mode 100644 index 000000000..b7d892840 --- /dev/null +++ b/hanami_application/config/settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Ecommerce + class Settings < Hanami::Settings + setting :database_url, constructor: Types::String + end +end diff --git a/hanami_application/db/migrate/20210806000000_create_ruby_event_store_tables.rb b/hanami_application/db/migrate/20210806000000_create_ruby_event_store_tables.rb new file mode 100644 index 000000000..f96263b2e --- /dev/null +++ b/hanami_application/db/migrate/20210806000000_create_ruby_event_store_tables.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +ROM::SQL.migration do + change do + create_table? :event_store_events_in_streams do + primary_key :id, type: :Bignum, null: false + + column :stream, String, null: false + column :position, Integer + + column :event_id, :uuid, null: false + + column :created_at, + DateTime, + null: false, + type: "TIMESTAMP", + index: "index_event_store_events_in_streams_on_created_at" + + index %i[stream position], unique: true, name: "index_event_store_events_in_streams_on_stream_and_position" + index %i[created_at], name: "index_event_store_events_in_streams_on_created_at" + index %i[stream event_id], unique: true, name: "index_event_store_events_in_streams_on_stream_and_event_id" + end + + create_table? :event_store_events do + primary_key :id, type: :Bignum, null: false + + column :event_id, :uuid, null: false + + column :event_type, String, null: false + + column :metadata, :jsonb + column :data, :jsonb, null: false + + column :created_at, + DateTime, + null: false, + type: "TIMESTAMP", + index: "index_event_store_events_on_created_at" + column :valid_at, + DateTime, + null: false, + type: "TIMESTAMP", + index: "index_event_store_events_on_valid_at" + + index %i[event_id], unique: true, name: "index_event_store_events_on_event_id" + index %i[created_at], name: "index_event_store_events_on_created_at" + index %i[valid_at], name: "index_event_store_events_on_valid_at" + index %i[event_type], name: "index_event_store_events_on_event_type" + end + end +end diff --git a/hanami_application/db/migrate/20231005225237_create_orders.rb b/hanami_application/db/migrate/20231005225237_create_orders.rb new file mode 100644 index 000000000..c87ec0e4d --- /dev/null +++ b/hanami_application/db/migrate/20231005225237_create_orders.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +ROM::SQL.migration do + change do + run 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp";' + + create_table(:orders) do + primary_key :id, :uuid, default: Sequel.function(:uuid_generate_v4) + + column :number, String + column :customer, String + column :state, String + + column :percentage_discount, BigDecimal, size: [8, 2] + column :total_value, BigDecimal, size: [8, 2] + column :discounted_value, BigDecimal, size: [8, 2] + column :happy_hour_value, BigDecimal, size: [8, 2] + + column :total_value_updated_at, DateTime + column :discount_updated_at, DateTime + end + end +end diff --git a/hanami_application/lib/tasks/.keep b/hanami_application/lib/tasks/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/hanami_application/spec/requests/orders/index_spec.rb b/hanami_application/spec/requests/orders/index_spec.rb new file mode 100644 index 000000000..7963a74d6 --- /dev/null +++ b/hanami_application/spec/requests/orders/index_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.describe "Submit Order", type: :request do + it "is successful" do + # Arrange + # Act + get "/" + + # Assert + expect(last_response).to be_successful + end +end diff --git a/hanami_application/spec/requests/orders/submit_spec.rb b/hanami_application/spec/requests/orders/submit_spec.rb new file mode 100644 index 000000000..803d9bb5c --- /dev/null +++ b/hanami_application/spec/requests/orders/submit_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.describe "Submit Order", type: :request do + it "is successful" do + # Arrange + order_id = SecureRandom.uuid + customer_id = SecureRandom.uuid + + Hanami.app["ecommerce.config"] + Hanami.app["command_bus"].(Crm::RegisterCustomer.new(customer_id: customer_id, name: 'Bruce Wayne')) + + # Act + post "/orders", { order_id: order_id, customer_id: customer_id} + + # Assert + response_uuid = JSON.parse(last_response.body)["id"] + expect(response_uuid).to eq(order_id) + + expect(last_response).to be_successful + end +end diff --git a/hanami_application/spec/spec_helper.rb b/hanami_application/spec/spec_helper.rb new file mode 100644 index 000000000..b0ce09239 --- /dev/null +++ b/hanami_application/spec/spec_helper.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "pathname" +SPEC_ROOT = Pathname(__dir__).realpath.freeze + +ENV["HANAMI_ENV"] ||= "test" +require "hanami/prepare" + +require_relative "support/rspec" +require_relative "support/requests" diff --git a/hanami_application/spec/support/requests.rb b/hanami_application/spec/support/requests.rb new file mode 100644 index 000000000..0816b8cd4 --- /dev/null +++ b/hanami_application/spec/support/requests.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "rack/test" + +RSpec.shared_context "Hanami app" do + let(:app) { Hanami.app.boot } +end + +RSpec.configure do |config| + config.include Rack::Test::Methods, type: :request + config.include_context "Hanami app", type: :request +end diff --git a/hanami_application/spec/support/rspec.rb b/hanami_application/spec/support/rspec.rb new file mode 100644 index 000000000..96349835b --- /dev/null +++ b/hanami_application/spec/support/rspec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + + config.filter_run_when_matching :focus + + config.disable_monkey_patching! + config.warnings = true + + if config.files_to_run.one? + config.default_formatter = "doc" + end + + config.profile_examples = 10 + + config.order = :random + Kernel.srand config.seed +end diff --git a/infra/lib/infra.rb b/infra/lib/infra.rb index 9791fcd80..a0c6cc7fd 100644 --- a/infra/lib/infra.rb +++ b/infra/lib/infra.rb @@ -17,4 +17,5 @@ require_relative "infra/process" require_relative "infra/types" require_relative "infra/testing" +require_relative "infra/transaction" require_relative "infra/event_handler" diff --git a/infra/lib/infra/transaction.rb b/infra/lib/infra/transaction.rb new file mode 100644 index 000000000..ab855bbd4 --- /dev/null +++ b/infra/lib/infra/transaction.rb @@ -0,0 +1,13 @@ +module Infra + class Transaction + def initialize(container:) + @container = container + end + + def call + @container.gateways[:default].transaction do + yield + end + end + end +end diff --git a/rails_application/lib/configuration.rb b/rails_application/lib/configuration.rb index 078f236c2..e30bbb62b 100644 --- a/rails_application/lib/configuration.rb +++ b/rails_application/lib/configuration.rb @@ -18,6 +18,8 @@ def call(event_store, command_bus) enable_authentication_read_model(event_store) Ecommerce::Configuration.new( + event_store: Rails.configuration.event_store, + command_bus: Rails.configuration.command_bus, number_generator: Rails.configuration.number_generator, payment_gateway: Rails.configuration.payment_gateway, available_vat_rates: [