Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Domain generators: contract, entity, install and operation. Update documentation #183

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 99 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# AcaEntities

A portable domain model for Benefit Management solutions.
A portable Domain model for Benefit Management solutions.

IdeaCrew defines a Canonical Vocabulay (CV) for structuring and
exchanging information between services for its benefit management
technology solutions. AcaEntities defines those domain model entities,
validation contracts, transformations with other vocabularies, and
other supporting artifacts.
IdeaCrew defines a Canonical Vocabulay (CV) for structuring and exchanging information between services for its benefit management technology solutions. AcaEntities defines those domain model entities, validation contracts, transformations with other vocabularies, and other supporting artifacts.

## Installation

Expand All @@ -18,112 +14,153 @@ gem 'aca_entities'

And then execute:

$ bundle install
```sh
$ bundle install
Bundle complete! NN Gemfile dependencies, NN gems now installed.
```

Or install it yourself as:

$ gem install aca_entities
```sh
$ gem install aca_entities
```

## Usage

AcaEntities is organized into a series of subject-area libraries that load only
the artifacts and dependencies associated with that subject. Each library includes
a ruby file for the namespace that requires all the depenencies for that library.
AcaEntities' canonical vocabularies help manage uniform, efficient data exchange across internal and external system services. Operations and helper features provide solutions for commonly-used data management functions. AcaEntties uses the [dry-rb](https://dry-rb.org/) gem library to support Domain-Driven Design and Clean Code tenets.

For example, {AcaEntities::Fdsh::Fdsh.rb} requires all dependencies to load the
FDSH namespace.
### Creating Domain Classes with `rails generate`

AcaEntities includes the following libraries:
Rails generators are command line tools that automate the process of creating or editing files with boilerplace code. AcaEntities provides Rails generators that support Domain-driven software development.

### {AcaEntities::Configuration::Encryption Encryption}
There are three primary `domain` generators: `domain:install, domain:entity` and `domain:operation`. These tools accelerate development by creating and configuring `Entity, Contract, Operation` and `Types` classes and associated Rspec files. They manage namespaces, dependencies and declarations consistent with IdeaCrew's Clean Code conventions. They also seed `yardoc` customization-ready content to easily produce development technical documentation.

Configuration setup to Encrypt and Decrypt sensitive information.
The `domain:install` generator configures a new Rails application (`domain_app` in this example) to use Domain components:

```sh
$ rails generate domain:install
create app/domain/contracts/contract.rb
create app/domain/types.rb
create app/domain/domain_app/types.rb
invoke rspec
create spec/domain/domain_app/types_spec.rb
```

Initializer file in other Applications(Rails App)
Use the `domain:entity` generator to create Domain Entity classes and associated attributes. This generator accepts arguments for an Entity name and an optional list of attributes and creates corresponding Entity and Contract files under the `app/domain` folder. Attribute arguments include a required attribute name value, with optional type and key parameters:

```sh
ATTRIBUTE_NAME[:type][:optional_key (default) | :required_key]
```

For example:

```sh
$ rails g domain:entity organization id:integer:required_key description:string name:string submitted_at:date_time
create app/domain/organization.rb
generate domain:contract
rails generate domain:contract organization id:integer:required_key description:string name:string submitted_at:date_time
create app/domain/contracts/organization_contract.rb
invoke rspec
create spec/domain/contracts/organization_contract_spec.rb
invoke rspec
create spec/domain/organization_spec.rb
```

Use the `domain:operation` generator to create an Operation class under the `app/operations` folder. Operations are named in imperative form as they function as commands or actions. Since Operations often publish an Event, the generated file includes references to EventSource dependencies. For example:

```sh
rails g domain:operation create_organization
create app/operations/create_organization.rb
invoke rspec
create spec/operations/create_organization_spec.rb
```

**Note:** executing `$ rails generate --help` on the command line will list all available generators.

### Protecting Sensitive Data Using Symmetic Encryption

AcaEntities includes encrypt/decrypt functions to encode PII and other senstive data content for transmission between services and for persisting to data stores. Applications and services can access this feature in two steps.

First, add or extend an AcaEntities initialize file (`config/initializers/aca_entities.rb`) with secure key configuration

```ruby
# config/initializers/aca_entities.rb

AcaEntities::Configuration::Encryption.configure do |config|
config.encrypted_key = ENV['SYMMETRIC_ENCRYPTION_ENCRYPTED_KEY']
config.encrypted_iv = ENV['SYMMETRIC_ENCRYPTION_ENCRYPTED_IV']
config.private_rsa_key = ENV['ENROLL_SYMMETRIC_ENCRYPTION_PRIVATE_KEY']
config.app_env = Rails.env
config.encrypted_key = ENV['SYMMETRIC_ENCRYPTION_ENCRYPTED_KEY']
config.encrypted_iv = ENV['SYMMETRIC_ENCRYPTION_ENCRYPTED_IV']
config.private_rsa_key = ENV['ENROLL_SYMMETRIC_ENCRYPTION_PRIVATE_KEY']
config.app_env = Rails.env
end
```

Operations to Encrypt and Decrypt
Second, call the appropriate Operation, passing data to be encrypted or decypted:

```ruby
encrypted_value = AcaEntities::Operations::Encryption::Encrypt(sensitive_data)
decrypted_value = AcaEntities::Operations::Encryption::Decrypt(encrypted_value)
```

## Vocabularies

Encrypt - `AcaEntities::Operations::SymmetricEncryption::Encrypt`
The `lib/aca_entities/libraries/` folder defines a `core_libary` and other namespaced libraries designed to load required artifacts and dependencies rather than the entire library. For example, `lib/aca_entities/libraries/fdsh_library.rb` loads only the FDSH namespace and dependencies necessary to support communication with CMS services.

Decrypt - `AcaEntities::Operations::SymmetricEncryption::Decrypt`
### Account Transfer Protocol

### {AcaEntities::Atp ATP}
Vocabularies and transforms that enable account and eligibility information exchange between FFE Account Transfer Protocol (ATP) and IdeaCrew's CV.

Vocabularies and transforms that enable account and eligibility information exchange
between FFE Account Transfer Protocol (ATP) and IdeaCrew's CV.
### AsynApi

### {AcaEntities::AsynApi AsynApi}
Shared configuration files that enable publication/subscribe (pub/sub) service producers and consumers to exchange messages between mircoservices. Files are organized by microservice and communication protocol.

Interface definitions for service producers and consumers to exchange messages.
Typically used by the {https://github.com/ideacrew/aca_entities.git EventSource gem}
these files follow the {https://www.asyncapi.com/docs/specifications/v2.0.0 AsyncAPI 2.0} standard.
These yaml-formatted definitions use the [AsyncAPI 2.0](https://www.asyncapi.com/docs/specifications/v2.0.0) specification to describe API endpoints in a protocol-neutral form. Tools like IdeaCrew's [EventSource gem](https://github.com/ideacrew/aca_entities.git) use these definitions to instantiate connections and channels between described services. Other tools may use them to create self-documenting API endpoints.

### {AcaEntities::Crms CRMs}
### CRMs

Integration with Customer Relationship Management (CRM) systems including SugarCRM.

### {AcaEntities::Fdsh FDSH}
### FDSH

Integration with CMS's Federal Data Sharing Hub (FDSH) that supports
eligibility determination and verification services necessary to operate a
State-based Exchange (SBE).
Integration with CMS's Federal Data Sharing Hub (FDSH) that supports eligibility determination and verification services necessary to operate a State-based Exchange (SBE).

### {AcaEntities::Google Google}
### Google

Integrations with Google Map services including interactive UI maps and
address geocoding services.
Integrations with Google Map services including interactive UI maps and address geocoding services.

### {AcaEntities::Ledger Ledger}
### Ledger

Integrations with accounting services including Quickbooks Online.

### {AcaEntities::MagiMedicaid MagiMedicaid}
### MagiMedicaid

Integrations and code that enable Affordable Care Act (ACA) financial
assistance eligibility determinations, including MAGI Medicaid, Advance
Premium Tax Credit (APTC) and Cost Sharing Reductions (CSRs)
Integrations and code that enable Affordable Care Act (ACA) financial assistance eligibility determinations, including MAGI Medicaid, Advance Premium Tax Credit (APTC) and Cost Sharing Reductions (CSRs)

## Development
## Contributing

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
AcaEnties vocabularies are based on [dry-rb](https://dry-rb.org/) libraries and support Domain-driven development. IdeaCrew conventions use Entity classes to define attributes and domain logic. Do not include data coercions or transformations in Entities. Rather, use Contracts and Operations to filter and shape data prior to instantiating Entity structs.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
Do include `yardoc` API documentation and Rspec specs for all Entities, Contracts, Operations and other content added to AcaEntities.

## Contributing
### Extending AcaEntities Domain Model

AcaEnties uses {https://dry-rb.org/ dry-rb} libriries to define domain entities:
{https://dry-rb.org/gems/dry-struct/1.0/ dry-struct} and validation contracts:
{https://dry-rb.org/gems/dry-validation/1.6/ dry-validation}.
Before adding new code to AcaEntties, review existing vocabularies to use or extend to support requirements. Start with the `lib/aca_entities/libraries/` folder. Next, search `lib/aca_entities` subfolders for suitable models. Note that some subfolders define vocabularies that aren't obvious from the folder name.

Entity definitions are flexible - nearly all define entities list each attribute as optional. Validation contracts, which can be applied based on specific scenarios,
are strict - attribute presence, coersion and type are specified at the contract
level only.
Projects that create new must use the `lib/aca_entities/libraries` and namespacing conventions to tie in the new vocaulary in a modular way. Projects that update existing vocabularies should include refactoring to do the same.

Include APi documentation and rspec specs for all entities, contracts, transforms
and other content added to AcaEntities.
### Issues

### Extending AcaEntities Domain Model
#### Known Issues

Before you add new code to AcaEntties, make sure existing domain models don't already
exist for your needs. Next, determine existing core model entities that you can reuse to build out the new namespace.
Some subject-specific vocabularies are unnecessarily loaded: 1) via `lib/aca_entities/libraries/core_library.rb`, or 2) via `lib/aca_entities.rb`. These should be refactored to use the `lib/aca_entities/libraries` convention to isolate the vocaulary and its dependencies to enable modular loading.

### Issues
#### Other Issues

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/aca_entities. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/aca_entities/blob/master/CODE_OF_CONDUCT.md).
Bug reports and pull requests are welcome on GitHub at [aca_entities](https://github.com/ideacrew/aca_entities). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ideacrew/aca_entities/blob/trunk/CODE_OF_CONDUCT.md).

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

## Code of Conduct

Everyone interacting in the AcaEntities project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/aca_entities/blob/master/CODE_OF_CONDUCT.md).
Everyone interacting in the AcaEntities project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ideacrew/aca_entities/blob/master/CODE_OF_CONDUCT.md).
89 changes: 89 additions & 0 deletions lib/generators/domain/contract/contract_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

require_relative '../generated_dry_attribute'

module Domain
# Generate a Domain Entity Contract file
class ContractGenerator < Rails::Generators::NamedBase # :nodoc:
source_root File.expand_path('templates', __dir__)

ENTITY_CONTRACT_PATH = 'app/domain/contracts'
ENTITY_CONTRACT_TEMPLATE_FILENAME = 'entity_contract.rb'
DOC_PREFIX_TEXT = <<~ATTR.chomp.strip
# @!method call(opts)
# @param [Hash] opts the parameters to validate using this contract
ATTR

desc 'Create Dry::Validations Contract file'

argument :arguments,
type: :array,
default: [],
banner:
'ATTRIBUTE_NAME[:type][:optional_key (default) | :require_key] ATTRIBUTE_NAME[:type][:optional_key (default) | :require_key]'

check_class_collision suffix: 'Contract'

def initialize(*args, &blk)
@local_args = args[0].dup
super(*args, &blk)

@local_class_name = class_name.to_s.split('::').last
@doc_content = DOC_PREFIX_TEXT
@params_content = ''
@indentation = 2
@class_indent = [class_path.size, 0].max
end

# Convert arguments array into GeneratedDryAttribute objects
def parse_dry_arguments
attrs = @local_args.drop(1)

(attrs || []).map do |attr|
parsed_attr = Generators::Domain::GeneratedDryAttribute.parse(attr)
@doc_content += doc_text(parsed_attr).chomp
@params_content += params_text(parsed_attr).chomp
end
end

def create_entity_contract_file
template ENTITY_CONTRACT_TEMPLATE_FILENAME, contract_filename
end

hook_for :test_framework, in: :rspec, as: :contract

private

def contract_filename
File.join(ENTITY_CONTRACT_PATH, class_path, "#{file_name}_contract.rb")
end

def params_text(attr)
attr.key_required? ? required_attribute(attr) : optional_attribute(attr)
end

def doc_text(attr)
attr.key_required? ? required_doc(attr) : optional_doc(attr)
end

def required_attribute(attr)
data_type = attr.type
"\nrequired(:#{attr.name}).filled(:#{data_type})"
end

def optional_attribute(attr)
data_type = attr.type.to_s
"\noptional(:#{attr.name}).maybe(:#{data_type})"
end

def required_doc(attr)
data_type = attr.type.to_s.classify
"\n# @option opts [#{data_type}] :#{attr.name.to_sym} (required)"
end

def optional_doc(attr)
data_type = attr.type.to_s.classify
"\n# @option opts [#{data_type}] :#{attr.name.to_sym} (optional)"
end
end
end
13 changes: 13 additions & 0 deletions lib/generators/domain/contract/templates/entity_contract.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

<% class_path.each_with_index do |path_fragment, index| -%>
<%= indent("module #{path_fragment.to_s.camelcase}", index * @indentation) %>
<% end -%>
<%= indent("# Schema, rules and macros for {#{class_name}} entity", @class_indent * @indentation) %>
<%= indent("class #{@local_class_name}Contract < Contracts::Contract", @class_indent * @indentation) %>
<%= indent(@doc_content, (@class_indent + 1) * (@indentation)) %>
<%= indent("params do", (@class_indent + 1) * (@indentation)) %>
<%= indent(@params_content.strip, (@class_indent + 2) * (@indentation)) %>
<% (0...(@class_indent + 2)).reverse_each do |index| -%>
<%= indent('end', index * @indentation) %>
<% end -%>
12 changes: 12 additions & 0 deletions lib/generators/domain/entity/USAGE
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Usage:
bin/rails generate domain:entity NAME [attribute[:type][:key_requirement] attribute[:type][:key_requirement]] [options]

Description:
Generate a Domain Entity file with optional attributes

Example:
bin/rails generate domain:entity account title:type:require_key description:type:optional_key

Creates an Account class and RSpec spec:
domain/account.rb
spec/domain/account_spec.rb
Loading