Skip to content

Responsability Centric with UseCase made easy

License

Notifications You must be signed in to change notification settings

croudcare/usecasing

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UseCase your code

Installation

Build Status Dependency Status Coverage Status Gem Version

Add this line to your application's Gemfile:

gem 'usecasing'

And then execute:

$ bundle

Usage

Let's build a Invoice System, right ?
So the product owner will create some usecases/stories to YOU.

Imagine this usecase/story:

As a user I want to finalize an Invoice and an email should be delivered to the customer.

Let's build a controller

	class InvoicesController < ApplicationController
		
		def finalize
		
		    params[:current_user] = current_user
   		    # params = { invoice_id: 123 , current_user: #<User:007> }
			context = FinalizeInvoiceUseCase.perform(params)
			
			if context.success?
				redirect_to invoices_path(context.invoice)
			else
				@errors = context.errors
				redirect_to invoices_path
			end
		
		end
		
	end

Ok, What is FinalizeInvoiceUseCase ?

FinalizeInvoiceUseCase will be responsible for perform the Use Case/Story.
Each usecase should satisfy the Single Responsability Principle and to achieve this principle, one usecase depends of others usecases building a Chain of Resposability.


	class FinalizeInvoiceUseCase < UseCase::Base
		depends FindInvoice, ValidateToFinalize, FinalizeInvoice, SendEmail
	end	
	

IMHO, when I read this Chain I really know what this class will do.
astute readers will ask: How FindInvoice pass values to ValidateToFinalize ?

When we call in the Controller FinalizeInvoiceUseCase.perform we pass a parameter (Hash) to the usecase.

This is what we call context, the usecase context will be shared between all chain.

	class FindInvoice < UseCase::Base
	
		def before
			@user = context.curent_user
		end
		
		def perform
		
			# we could do that in one before_filter
			invoice = @user.invoices.find(context.invoice_id)
			
			# asign to the context make available to all chain
			context.invoice = invoice
			   
		end

	end

Is the invoice valid to be finalized ?

	class ValidateToFinalize < UseCase::Base
		
		def perform
			#failure will stop the chain flow and mark the context as error.
			
			failure(:validate, "#{context.invoice.id} not ready to be finalized") unless valid?
		end
		
		private
		def valid?
			#contextual validation to finalize an invoice
		end
	end

So, after validate, we already know that the invoice exists and it is ready to be finalized.

	class FinalizeInvoice < UseCase::Base
		
		def before
			@invoice = context.invoice
		end
		
		def perform
			@invoice.finalize! #update database with finalize state
			context.customer = invoice.customer
		end
	
	end

Oww, yeah, let's notify the customer

	class SendEmail < UseCase::Base
	
		def perform
			to = context.customer.email
			
			# Call whatever service
			EmailService.send('customer_invoice_template', to, locals: { invoice: context.invoice } )
		end
	
	end

Stopping the UseCase dependencies Flow

There are 2 ways to stop the dependency flow.

  • stop! ( stop the flow without marking the usecase with error )
  • failure ( stop the flow but mark the usecase with errors )

Imagine a Read Through Cache Strategy. How can we stop the usecase flow without marking as failure ?

   class ReadThrough < UseCase::Base
      depends MemCacheReader, DataBaseReader, MemCacheWriter
   end
  
   class MemCacheReader < UseCase::Base
     def perform
       context.data = CacheAdapter.read('key')
       stop! if context.data
     end
   end

   class DataBaseReader < UseCase::Base
     def perform
       context.data = DataBase.find('key')
     end
   end
   
   class MemCacheWriter < UseCase::Base
     def perform
       CacheAdapter.write('key', context.data);
     end
   end

Let me know what do you think about it.

UseCase::Base contract

        # None of those methods are required.
         

	class BusinessRule < UseCase::Base
	  
	  def before
	    # executed before perform
	  end
	  
	  def perform
	    # execute the responsability that you want
	  end
	  
	  def rollback
	   # Will be called only on failure
	  end
	  
	end


TODO

Create real case examples (40%)

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

About

Responsability Centric with UseCase made easy

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 100.0%