rddd 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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