- Solution Overview
- Technical/Architectural Reasoning
- Technical/Architectural Reflections
- Script Execution
The RPC project consists of:
- Two primary class categories.
- Several secondary class categories whose sole purpose is to facilitate the functionality of the primary classes.
- A series of support classes/modules that assist with the overall funcionality of this project.
- Configuration files used to dynamically configure this project.
- An executable command-line script that allows Users to run and interact with the RPN Calculator from the console.
Primary Class Category | Example Class | High-Level Function |
---|---|---|
IO Interfaces | RealPage::Calculator::IOInterface RealPage::Calculator::ConsoleInterface |
IO Interface class objects (IO Interface) are responsible for the communication between the stream (input, output, error) and the Calculator Service class object (Calculator Service) and vise versa. |
Calculator Services | RealPage::Calculator::CalculatorService RealPage::Calculator::RPNCalculatorService |
Calculator Services are responsible for computing the input received from the IO Interface and returning the result and/or any errors encountered back to the IO Interface. |
Secondary Class Category | Example Class | High-Level Function |
---|---|---|
Calculator Results | RealPage::Calculator::CalculatorResults |
CalculatorResult class objects (CalculatorResult) are returned to the IO Interace as a result of a Calculator Service request initiated by the IO Interface. The CalculatorResult contains the computed result and/or any error that may have been encountered during the request. |
Input Parsers | RealPage::Calculator::InputParser RealPage::Calculator::RPNInputParser |
InputParser class objects (InputParser) are used by Calculator Services and responsible for parsing input received from an IO Interface into a format suitable for processing by the Calculator Service. |
Input Tokens | RealPage::Calculator::InputToken |
InputToken class objects (InputToken) represent an individual, space delimited token received from the input stream. InputToken is responsible for identifying the nature and type of the token it encapsulates. For example, InputToken identifies the following token types: operators, operands and commands; it also identifies whether or not a token is valid. |
Support Class/Module | High-Level Function |
---|---|
RealPage::Calculator::Configuration |
The Configuration Singleton object (Configuration) is responsible for loading the /calculator/config/calculator_service_config.yml file and providing a single(ton) interface to configuration settings user by Calculator Services. Configuration settings such as valid operators, commands (i.e. as quit, view stack, clear stack) and console_interface_options are made available through the Configuration Singleton. |
RealPage::Calculator::I18nTranslator |
The i18n Tranclator Singleton object (i18n Translator) is responsible for loading the /configuration/config/i18n.yml file and providing a single(ton) interface for translation services used by IO Interfaces. |
RealPage::Calculator::InterfaceNotReadyError RealPage::Calculator::MustOverrideError |
The Error class objects (Errors) are used throughout the RPC project wherever a custom error needs to be raised. |
RealPage::Calculator::Messages |
The Message Support module (Message Support) defines errors and warnings in the form of Hash values. When the Calculator Service encounters an error or warning, a CalculatorResult object is created and the Message Support message is embedded in the CalculatorResult object and sent to the IO Interface. The Message Support message Hash embedded in the CalculatorResult object is then used by the IO Interface as input to the i18n Translator ({type: :error | :warning, key: :key, scope: [:scope1, :scope2, ...]}) to return a locale specific message to the output stream. |
RealPage::Calculator::Helpers::Blank RealPage::Calculator::Helpers::Arrays |
These Helper modules (Helpers) add convenience functionality in support of this project. |
File | High-Level Purpose |
---|---|
/calculator/config/calculator.yml |
The Calculator Configuration file is used to configure operators and command-line commands that will be acknowledged by the CalculatorServices and IO Interfaces, as well as options to configure specific IO Interface behavior. |
/calculator/config/i18n.yml |
The i18n Configuration file provides locale-specific translation entries in the form of key/scope pairs used by the i18n Translator |
Script | Type | Purpose |
---|---|---|
/calculator/rpn_cal.rb |
Ruby | The RPN Calculator file is an executable Ruby script that Users can use to run and interact with the RPN calculator either in a UNIX-like CLI (console interface), or via input file (file interface). |
According to the specification, the RPC project was to create a command-line, Reverse Polish Notation calculator for people who are comfortable with UNIX-like CLI utilities. The specification also dictated that the initial project installment must implement the four basic calculator operators (+, -, /, *) and be designed in such a way as to allow for additional operators and interfaces (WebSocket, file or TCP Socket) to be added in the future. In addition to these, I took the liberty of presuming the possibility of additional calculator types as well. Consequently, I sought to design this RPC project in such a way as to be (among other things):
- Configurable (additional operators)
- Extensible (additional IO Interfaces, Calculator Service types)
- DRY
- Testable
From an architectural perspective, the RPC project consists of a series of what will be referred to (arbitrarily) as:
- Primary class categories
- Secondary class categories
- Support classes/modules
- Executable command-line scripts
The remainder of this section will give an overview of each, as well as the technical/architectural reasoning behind the same.
RealPage::Calculator::CalculatorService
RealPage::Calculator::RPNCalculatorService
RealPage::Calculator::IOInterface
RealPage::Calculator::ConsoleInterface
Primary class categories include IO Interfaces and Calculator Services. Classes that derive from RealPage::Calculator::IOInterface
and RealPage::Calculator::CalculatorService
, respectfully, fall into these categories. IO Interfaces and Calculator Services are considered Primary classes because these are the categories of classes Developers and Users will interact with most often.
The IO Interface acts as a liaison between the Calculator Service and the particular stream the IO Interface represents. Consequently, the IO Interface is also responsible for the format (plain text, json, xml, etc.) and translation of all output sent to the output stream using i18n.
The Calculator Service is a service responsible for accepting input from, and returning a result (computation or error) to, the IO Interface. Consequently, the Calculator Service, by implementing an Input Parser, is responsible for manipulating the raw input received from the IO Interface into a format specific to its own needs, and returning the raw result (CalculatorResult object) back to the IO Interface.
The IO Interface and Calculator Service are interdependent; therefore, they need to communicate with eachother. However, the IO Interface and Calculator Service have distinct concerns. As a result, the decision was made to create two categories of classes - each concerned solely with its own, distinct, funcionality - maintaining a clear separation of concern. In addition to this, the decision was made to require that a RealPage::Calculator::CalculatorService
object be provided when instantiating a RealPage::Calculator::IOInterface
object. This losely coupled relationship (as opposed to Composition) makes for a more extensible, testable, and potentially DRY framework, and opens up the ability for future non-RPN-based Calculator Services to be implemented using existing IO Interfaces. Similar rationale and implementation can be witnessed between the Calculator Service -> Input Parser relationship (i.e. RealPage::Calculator::CalculatorService
, RealPage::Calculator::InputParser
)
RealPage::Calculator::CalculatorResult
RealPage::Calculator::InputParser
RealPage::Calculator::RPNInputParser
RealPage::Calculator::InputToken
A Calculator Result is returned via notification from a Calculator Service to an IO Service in response to a Calculator Service request (e.g. RealPage::Calculator::CalculatorService#compute
). A Calculator Result object encapsulates a Calculator Service computation or error encountered along with the offending token. Calculator Result provides a standard container class for communicating results from the Calculator Service to the IO Interface.
Encapsulating the result returned from a Calculator Service (in a Calculator Result object) enforces a standard in the way Calculator Services and IO Interfaces exchange Calculator Service results. Calculator Result provides rudimentary methods to retrieve computed results and identify/retrieve errors when they occur (single responsibility). Calculator Result can be easily extended to provide additional information or functionality should it be required by a future Calculator Service or IO Interface. Finally, in addition to providing extensibility, Calculator Result classes are testable, and its enforced use helps to maintain the DRY principle.
Input Parsers are used by Calculator Services. A RealPage::Calculator::CalculatorService
, in fact, cannot be instantiated without providing an RealPage::Calculator::InputParser
. Input Parsers are responsible for parsing input received from an IO Interface into a format suitable for processing by the Calculator Service.
Calculator Services have the single responsibility of computing. Likewise, Input Parsers have the single responsibility of parsing input received from an IO Interface into a format suitable for processing by the Calculator Service. Input Parsers relieve the Calculator Service of this burden by eliminating this concern. Input Parsers help keep things DRY as they may be shared between Calculator Services or extended. In addition to this, Input Parsers provide better, isolated testability.
Input Tokens are used by classes derived from RealPage::Calculator::InputParser
and represent an individual, space delimited token received from the input stream. Input Token is responsible for identifying the nature and type of the token it encapsulates. For example, Input Token identifies the following token types: operators, operands and commands; it also identifies whether or not a token is valid.
Input Tokens exist to identify the nature (#empty?
, #invalid?
, #valid?
) and type (#command?
, #operator?
, #operand?
, #quit?
, etc.) of each input token encountered; this relieves the other classes of this responsibility and makes for better testability. Input Token provides the same class methods as the instance implementation so that input may be interrogated without the need to instantiate an Input Token object.
RealPage::Calculator::Configuration
RealPage::Calculator::I18nTranslator
RealPage::Calculator::InterfaceNotReadyError
RealPage::Calculator::MustOverrideError
RealPage::Calculator::Messages
RealPage::Calculator::Helpers::Blank
RealPage::Calculator::Helpers::Arrays
The functionality that each of these classes/modules provides is necessary to the RPC project. However, that doesn't justify the existance of any of these classes/modules in particular - this is true of any class/module in this project. In general, however, the justification for these particular classes/modules primarily includes the need to separate the concern each class has from the rest of the application, and to limit this concern to a single responsibility. The reason they are coded the way they are, depends on their individual purpose.
The RealPage::Calculator::Configuration
class provides application configuration settings. This is necessary to make the project more dynamic. For example, you should not have to change the program code in order to add a new operator, or change the value of the quit command. It is a Singleton. It is also a Singleton because only one instance of this class ever needs to exist. This is because the data it makes available never changes after the class has been instantiated. It is not a Module, because it needs to load a yaml file as part of its instantiation processing and it makes the most sense to do this one time, during object instantiation. Modules do not get instantiated. This is not regular a Class either, for slightly different reasons; it doesn't make sense to instantiate multiple objects of this types, only to have to load the yaml file over and over.
The RealPage::Calculator::I18nTranslator
classes provides i18n translation key/scope pairs used for text translation. This is necessary to provide localization. For example, if I speak Spanish and am using the RPN Calculator, I should see error messages in Spanish. It also is a Singleton for the same reasons mentioned previously.
RealPage::Calculator::Messages
is a module. Likewise, the data it makes available never changes; however, the data it makes available is static (not dependant upon loading a yaml file). Therefore, a Module makes the most sense.
The RealPage::Calculator::InterfaceNotReadyError
and RealPage::Calculator::MustOverrideError
error classes provide custom errors where the standard Ruby errors fall short.
RealPage::Calculator::Helpers
is a convenience module. The RealPage::Calculator::Helpers::Blank
module can be included to mix in the #blank? method, which can be used to determine whether or not an object is nil or empty This method is used by the Input Parser classes to determine whether or not the input it receives is nil or empty. Likewise, the RealPage::Calculator::Helpers::Arrays
is also a module that may be mixed into a class to take advantage of the #upper_bound member that returns the upper bound of an Array.
/calculator/rpn_calc.rb
The justification for this script is that Users need a simple script to run the RPN calculator from the command-line via input or command-line via input file. This script enables users to do that without having to start irb and instantiate
There are a few things I would do differently if I spent more time on the project. The first thing I would do, is make this project a Ruby gem. A gem would enable this project to be distributed properly and easily incorporated into any Ruby or Rails application. In fact, I would most likely break the project up into multiple gems, for example, an rpc_core gem and perhaps one gem for each additional IO Interface type. This would also eliminate the need to hard-code yaml file names in the RealPage::Calculator::Configuration
and RealPage::Calculator::I81nTranslator
classes and probably eliminate these classes altogether in favor of Ruby standard config scripts.
Some of the other things I would do or do differently would be:
- Create additional IO Interfaces, for example, a WebSocket (I've never use WebSocket before), then create a Rails app host and see how it works.
- Refactor
RealPage::Calculator::CalculatorService
to allow input stack as a param during initialization so the Calculator Service state could be restored in a stateless environment, HTTP Interface for example. - Provide a means of setting (in the case of stateful environments) or accepting (in the case of stateless environments) a locale to be used for localization.
- Refactor
RealPage::Calculator::InputToken
into a base class by eliminating calculator-specific command methods (class and instance) and force Calculator Services to implement their own, calculator-specific InputToken class. It's not very extensible the way it is. - Refactor
RealPage::Calculator::InputParser
to allow a token delimiter param during initialization to be used to parse and tokenize raw input; currently, only spaces are recognized. - Add a help command to be associated with
RealPage::Calculator::RPNCalculatorService
that returns a lists all available commands and in the case of a Console Interface, displays help in a UNIX-like CLI fashion.
There are two ways to run the RPN calculator using the console as the interface, Rake task and Ruby script. The instructions for each are outlined below.
For your convenience, view stack (v) and clear stack (c) commands have been implemented. These RPN calculator commands allow you to view the input stack and clear the input stack respectfully.
To run a version of the RPN calculator from the console using Rake, make sure you are in the project root directory (/realpage_calculator
), then type either of the following command into the command-line followed by ENTER:
$ rake console
$ rake file
The $ rake console
command runs the RPN Calculator using the console and allows the user to interact with the calculator via user input into the terminal. The $ rake file
command also runs the RPN Calculator using the console; however, this command uses the spec/files/rpn_input_file.txt
file as input to the RPN Calculator.
To run a version of the RPN calculator from the console using Ruby script, from the project root folder (/realpage_calculator
), change to the /calculator
folder by typing the following into the command-line followed by ENTER:
$ cd calculator
Now type one of the following command into the command-line followed by ENTER to run the RPN calculator:
$ ruby rpn_calc.rb
$ ruby rpn_calc.rb spec/files/rpn_input_file.txt
The $ ruby rpn_calc.rb
command runs the RPN Calculator using the console and allows the user to interact with the calculator via user input into the terminal. The $ ruby rpn_calc.rb spec/files/rpn_input_file.txt
command also runs the RPN Calculator using the console; however, this command uses the spec/files/rpn_input_file.txt
file as input to the RPN Calculator.
If you don't wish to have to type $ ruby
followed by the the script name every time you run this script, from the /calculator
folder, type the following into the command-line followed by ENTER:
$ chmod 755 rpn_calc.rb
You may now execute the rpn_calc.rb
script by typing the following into the command-line followed by ENTER:
$ ./rpn_calc.rb