rddd 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ rddd.sublime*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby-1.9.3-p194@rddd
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rddd.gemspec
4
+ gemspec
5
+
6
+ group :testing do
7
+ gem 'rspec'
8
+ gem 'mocha'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rddd (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.3)
10
+ metaclass (0.0.1)
11
+ mocha (0.12.7)
12
+ metaclass (~> 0.0.1)
13
+ rspec (2.11.0)
14
+ rspec-core (~> 2.11.0)
15
+ rspec-expectations (~> 2.11.0)
16
+ rspec-mocks (~> 2.11.0)
17
+ rspec-core (2.11.1)
18
+ rspec-expectations (2.11.3)
19
+ diff-lcs (~> 1.1.3)
20
+ rspec-mocks (2.11.3)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ mocha
27
+ rddd!
28
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Petr Janda
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ rddd
2
+ ====
3
+
4
+ Ruby DDD (Domain Driven Development) framework
5
+
6
+ Intention of rddd is to provide basic skeleton for DDD ruby application. Its framework agnostic, although some framework specific extensions might come later to make it easier to start.
7
+
8
+ ## Key goals
9
+
10
+ * Provide basic DDD elements
11
+ * Support separation of domain from delivery mechanism (MVC framework)
12
+ * Support separation of domain from persistancy mechanism
13
+
14
+ ## Basic elements
15
+
16
+ As defined in DDD itself, framework has couple basic object types:
17
+
18
+ * Services - use cases
19
+ * Entities - domain objects with identity
20
+ * Aggregate roots - entities roots to setup consistency boundaries
21
+ * Repositories - abstraction over persistancy providing collections of aggregate roots
22
+
23
+ Alongside core DDD object types, there is couple supporting ones, mostly factories.
24
+
25
+ ### Service bus
26
+
27
+ To further enhance the separation of delivery mechanism and domain from each other, every call to the domain service is done through the ```Service bus```. The key design goal is to decouple concrete service implementation from the non-domain code, so they never have to be directly instantiated outside the domain itself. Instead, service bus defines simple protocol for any object, which can call its ```execute``` method with service name and parameters. The instantiation of the service object then becomes implementation detail of domain code, thus leaves the flexibility for alternative solutions within domain.
28
+
29
+ ## Project structure
30
+
31
+ Recommented project file structure:
32
+
33
+ app/entities
34
+ app/repositories
35
+ app/services
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/rddd.rb ADDED
@@ -0,0 +1,3 @@
1
+ require "rddd/version"
2
+
3
+ module Rddd; end
@@ -0,0 +1,15 @@
1
+ require 'rddd/entity'
2
+ require 'rddd/repository_factory'
3
+ require 'rddd/aggregate_root_finders'
4
+
5
+ class AggregateRoot < Entity
6
+ extend AggregateRootFinders
7
+
8
+ finder :find_by_id
9
+
10
+ [:create, :update, :delete].each do |action|
11
+ define_method action do
12
+ self.class.repository.send(action, self)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module AggregateRootFinders
2
+ def finder(name)
3
+ define_singleton_method name do |*args|
4
+ repository.send(name, *args)
5
+ end
6
+ end
7
+
8
+ def repository
9
+ RepositoryFactory.build(self)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Entity
2
+ attr_reader :id
3
+
4
+ def initialize(id)
5
+ @id = id
6
+ end
7
+
8
+ def ==(b)
9
+ @id == b.id
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ #
2
+ # Repository base class.
3
+ #
4
+ class Repository
5
+ end
@@ -0,0 +1,18 @@
1
+ require 'rddd/repository'
2
+
3
+ class NotExistingRepository < RuntimeError
4
+ end
5
+
6
+ class RepositoryFactory
7
+ def self.build(clazz)
8
+ repository_name = "#{clazz}Repository"
9
+
10
+ begin
11
+ repository = const_get(repository_name)
12
+ rescue
13
+ raise NotExistingRepository unless repository
14
+ end
15
+
16
+ repository.new
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Service is the object to represent a single use case. Service is usually good
3
+ # entry point to the system domain. Its responsibility is to orchestrate the
4
+ # cooperation of multiple entities, in a given order defined by use case.
5
+ #
6
+ # Service takes a list of attributes, which are necessary to execute the given
7
+ # use case. At the end nothing is returned, so service represent the command to
8
+ # the domain, but doesn't return any data back.
9
+ #
10
+ class Service
11
+ def initialize(attributes = {})
12
+ @attributes = attributes
13
+ end
14
+
15
+ def valid?
16
+ true
17
+ end
18
+
19
+ def execute
20
+ raise NotImplementedError
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'rddd/service_factory'
2
+
3
+ class InvalidService < RuntimeError; end
4
+
5
+ #
6
+ # Service bus is the central entry point for execution of service within the
7
+ # domain layer. Unless you have a very good reason, services should not be
8
+ # instantiated directly outside the domain layer, as you leave the flexibility
9
+ # on domain itself to choose the correct implementation.
10
+ #
11
+ #
12
+ # ## Usage
13
+ #
14
+ # Service bus as the module could be included to any object which intend to
15
+ # call services within the domain layer.
16
+ #
17
+ #
18
+ # ## Example
19
+ #
20
+ # class ProjectsController
21
+ # include ServiceBus
22
+ #
23
+ # def create
24
+ # execute(:create_project, params) do |errors|
25
+ # render :new, :errors => errors
26
+ # return
27
+ # end
28
+ #
29
+ # redirect_to projects_path, :notice => 'Project was successfully created!'
30
+ # end
31
+ # end
32
+ #
33
+ #
34
+ module ServiceBus
35
+ #
36
+ # Execute the given service.
37
+ #
38
+ # @param {Symbol} Service to be executed.
39
+ # @param {Hash} Attributes to be passed to the service call.
40
+ # @param {Block} Optional error callback block.
41
+ #
42
+ def execute(service_name, attributes = {})
43
+ raise InvalidService unless service = build_service(service_name, attributes)
44
+
45
+ unless service.valid?
46
+ yield(service.errors) and return if block_given?
47
+ end
48
+
49
+ service.execute
50
+ end
51
+
52
+ private
53
+
54
+ def build_service(service_name, attributes)
55
+ ServiceFactory.build(service_name, attributes)
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ class ServiceFactory
2
+ def self.build(name, attributes)
3
+ class_name = "#{camel_case(name.to_s)}Service"
4
+
5
+ Object.const_get(class_name.to_sym).new(attributes)
6
+ end
7
+
8
+ def self.camel_case(string)
9
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
10
+ string.split('_').map{|e| e.capitalize}.join
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Rddd
2
+ VERSION = "0.1.0"
3
+ end
data/rddd.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rddd/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rddd"
8
+ gem.version = Rddd::VERSION
9
+ gem.authors = ["Petr Janda"]
10
+ gem.email = ["petrjanda@me.com"]
11
+ gem.description = %q{Ruby DDD framework}
12
+ gem.summary = %q{Intention of rddd is to provide basic skeleton for DDD ruby application. Its framework agnostic, although some framework specific extensions might come later to make it easier to start.}
13
+ gem.homepage = "http://blog.ngneers.com"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'rddd/service'
3
+ require 'rddd/aggregate_root'
4
+ require 'rddd/service_bus'
5
+ require 'rddd/service'
6
+
7
+ class Project < AggregateRoot
8
+ attr_accessor :name
9
+ end
10
+
11
+ class CreateProjectService < Service
12
+ def execute
13
+ project = Project.new(@attributes[:id])
14
+ project.name = @attributes[:name]
15
+
16
+ project.create
17
+ end
18
+ end
19
+
20
+ class ProjectRepository
21
+ def create(project)
22
+
23
+ end
24
+ end
25
+
26
+ class ProjectsController
27
+ include ServiceBus
28
+
29
+ def create(params)
30
+ execute(:create_project, params)
31
+ end
32
+ end
33
+
34
+ describe 'CreateProject' do
35
+ it 'should call project save' do
36
+ ProjectRepository.any_instance.expects(:create)
37
+
38
+ ProjectsController.new.create(:id => 1, :name => 'Rddd')
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ require 'rddd/aggregate_root'
3
+
4
+ describe AggregateRoot do
5
+ let(:id) { stub('id') }
6
+
7
+ let(:aggregate_root) { AggregateRoot.new(id) }
8
+
9
+ it 'should be entity' do
10
+ aggregate_root.should be_kind_of Entity
11
+ end
12
+
13
+ describe '#find_by_id' do
14
+ subject { AggregateRoot.find_by_id(id) }
15
+
16
+ let(:repository) { stub('repository') }
17
+
18
+ before {
19
+ RepositoryFactory.expects(:build).returns(repository)
20
+ repository.expects(:find_by_id).with(id)
21
+ }
22
+
23
+ it { subject }
24
+ end
25
+
26
+ [:create, :update, :delete].each do |action|
27
+ describe "##{action}" do
28
+ subject { aggregate_root.send(action) }
29
+
30
+ let(:repository) { stub('repository') }
31
+
32
+ it 'should call #create on repository' do
33
+ RepositoryFactory.expects(:build).with(AggregateRoot).returns(repository)
34
+
35
+ repository.expects(action).with(subject)
36
+
37
+ subject
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'rddd/entity'
3
+
4
+ describe Entity do
5
+ it 'should have identity' do
6
+ entity = Entity.new('1234')
7
+
8
+ entity.id.should == '1234'
9
+ end
10
+
11
+ it 'two entities with same identity are equal' do
12
+ a = Entity.new('1234')
13
+ b = Entity.new('1234')
14
+
15
+ (a == b).should be_true
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'rddd/entity'
3
+ require 'rddd/repository_factory'
4
+
5
+ describe RepositoryFactory do
6
+ describe '.build' do
7
+ subject { RepositoryFactory.build(Entity) }
8
+
9
+ context 'with existing repository' do
10
+ before { Object.const_set(:EntityRepository, Class.new) }
11
+
12
+ after { Object.class_eval{remove_const :EntityRepository} }
13
+
14
+ it 'should return instance of repository' do
15
+ should be_kind_of EntityRepository
16
+ end
17
+ end
18
+
19
+ context 'with not existing repository' do
20
+ it 'should raise NotExistingRepository' do
21
+ lambda { subject }.should raise_exception NotExistingRepository
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'rddd/repository'
3
+
4
+ describe Repository do
5
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'rddd/service_bus'
3
+
4
+ describe ServiceBus do
5
+ let(:attributes) { stub('attributes') }
6
+
7
+ let(:controller) do
8
+ stub('controller').tap { |controller| controller.extend ServiceBus }
9
+ end
10
+
11
+ describe '.execute' do
12
+ subject { controller.execute(:foo, attributes) }
13
+
14
+ let(:service) { stub('service', :valid? => true, :execute => nil) }
15
+
16
+ context 'existing service' do
17
+ it do
18
+ ServiceFactory.expects(:build).with(:foo, attributes).returns(service)
19
+ service.expects(:execute)
20
+
21
+ subject
22
+ end
23
+ end
24
+
25
+ context 'not-existing service' do
26
+ it do
27
+ ServiceFactory.expects(:build).with(:foo, attributes).returns(nil)
28
+ service.expects(:execute).never
29
+
30
+ lambda { subject }.should raise_exception InvalidService
31
+ end
32
+ end
33
+
34
+ context 'not valid call to service' do
35
+ context 'without block' do
36
+ before { service.stubs(:valid?).returns(false) }
37
+
38
+ it do
39
+ ServiceFactory.expects(:build).with(:foo, attributes).returns(service)
40
+ service.expects(:execute).never
41
+
42
+ subject
43
+ end
44
+ end
45
+
46
+ context 'with error callback block' do
47
+ let(:errors) { stub('errors') }
48
+
49
+ before do
50
+ service.stubs(:valid?).returns(false)
51
+ service.stubs(:errors).returns(errors)
52
+ end
53
+
54
+ let(:error_block) { lambda {|errors|} }
55
+
56
+ it do
57
+ ServiceFactory.expects(:build).with(:foo, attributes).returns(service)
58
+ service.expects(:execute).never
59
+
60
+ controller.execute(:foo, attributes, &error_block)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'rddd/service_factory'
3
+
4
+ describe ServiceFactory do
5
+ let(:attributes) { stub('attributes') }
6
+
7
+ before { Object.const_set(:CreateProjectService, Class.new) }
8
+
9
+ after { Object.class_eval {remove_const(:CreateProjectService)} }
10
+
11
+ describe '.build' do
12
+ it do
13
+ CreateProjectService.expects(:new).with(attributes)
14
+
15
+ ServiceFactory.build(:create_project, attributes)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'rddd/service'
3
+
4
+ describe Service do
5
+ let(:attributes) { stub('attributes') }
6
+
7
+ describe '#initialize' do
8
+ subject { Service.new(attributes) }
9
+
10
+ it 'should store attributes' do
11
+ subject.instance_variable_get(:@attributes).should == attributes
12
+ end
13
+ end
14
+
15
+ describe '#valid?' do
16
+ subject { Service.new.valid? }
17
+ it { should be_true }
18
+ end
19
+
20
+ describe '#execute' do
21
+ it 'should raise not implemented' do
22
+ lambda do
23
+ Service.new(attributes).execute
24
+ end.should raise_exception(NotImplementedError)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'rspec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ # Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :mocha
12
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rddd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Petr Janda
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-03 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Ruby DDD framework
15
+ email:
16
+ - petrjanda@me.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .rspec
23
+ - .rvmrc
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - lib/rddd.rb
30
+ - lib/rddd/aggregate_root.rb
31
+ - lib/rddd/aggregate_root_finders.rb
32
+ - lib/rddd/entity.rb
33
+ - lib/rddd/repository.rb
34
+ - lib/rddd/repository_factory.rb
35
+ - lib/rddd/service.rb
36
+ - lib/rddd/service_bus.rb
37
+ - lib/rddd/service_factory.rb
38
+ - lib/rddd/version.rb
39
+ - rddd.gemspec
40
+ - spec/integration_spec.rb
41
+ - spec/lib/aggregate_root_spec.rb
42
+ - spec/lib/entity_spec.rb
43
+ - spec/lib/repository_factory_spec.rb
44
+ - spec/lib/repository_spec.rb
45
+ - spec/lib/service_bus_spec.rb
46
+ - spec/lib/service_factory_spec.rb
47
+ - spec/lib/service_spec.rb
48
+ - spec/spec_helper.rb
49
+ homepage: http://blog.ngneers.com
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 1.8.24
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Intention of rddd is to provide basic skeleton for DDD ruby application.
73
+ Its framework agnostic, although some framework specific extensions might come later
74
+ to make it easier to start.
75
+ test_files:
76
+ - spec/integration_spec.rb
77
+ - spec/lib/aggregate_root_spec.rb
78
+ - spec/lib/entity_spec.rb
79
+ - spec/lib/repository_factory_spec.rb
80
+ - spec/lib/repository_spec.rb
81
+ - spec/lib/service_bus_spec.rb
82
+ - spec/lib/service_factory_spec.rb
83
+ - spec/lib/service_spec.rb
84
+ - spec/spec_helper.rb