From dd9c519dd64949802051e162dbe720f83650e5f3 Mon Sep 17 00:00:00 2001 From: Stephen Fuqua Date: Tue, 6 Sep 2022 15:24:14 -0500 Subject: [PATCH] [RND-344] Documentation updates in preparation for 0.2.0. (#94) * Pointing the instructions to the folder instead of the readme file for clarity * Modifications to localhost instructions * Clarification about which .env file(s) to modify * Markdown cleanup * Remove obsolete document * Rewrite main readme * Additional notes and formatting * Refactor note on Resource Ids Formatting * Typo fix Co-authored-by: AndonyNS --- .../docker/readme.md | 6 +- .../docker/readme.md | 5 +- README.md | 25 +- docs/DEPLOYMENT.md | 283 ------------------ docs/LOCALHOST.md | 34 ++- docs/README.md | 20 +- docs/TECHNICAL.md | 114 ++++--- 7 files changed, 131 insertions(+), 356 deletions(-) delete mode 100644 docs/DEPLOYMENT.md diff --git a/Meadowlark-js/backends/meadowlark-opensearch-backend/docker/readme.md b/Meadowlark-js/backends/meadowlark-opensearch-backend/docker/readme.md index fd99d880..a4c7a8f2 100644 --- a/Meadowlark-js/backends/meadowlark-opensearch-backend/docker/readme.md +++ b/Meadowlark-js/backends/meadowlark-opensearch-backend/docker/readme.md @@ -3,10 +3,10 @@ :exclamation: This solution should only be used on localhost with proper firewalls around external network access to the workstation. Not appropriate for production use. -This Docker Compose file provisions a single node of the OpenSearch search engine and [OpenSearch - Dashboard](http://localhost:5601/) (latest versions). +This Docker Compose file provisions a single node of the OpenSearch search +engine and [OpenSearch Dashboard](http://localhost:5601/) (latest versions). -### Visualizations in OpenSearch Dashboards +## Visualizations in OpenSearch Dashboards Once data starts flowing into OpenSearch, you can setup some basic visualizations with the OpenSearch Dashboards. Basic steps: diff --git a/Meadowlark-js/backends/meadowlark-postgresql-backend/docker/readme.md b/Meadowlark-js/backends/meadowlark-postgresql-backend/docker/readme.md index 34c8c057..ec4ec204 100644 --- a/Meadowlark-js/backends/meadowlark-postgresql-backend/docker/readme.md +++ b/Meadowlark-js/backends/meadowlark-postgresql-backend/docker/readme.md @@ -7,9 +7,12 @@ This Docker Compose file provisions a single instance of PostgreSQL 14. ## Preparatory Steps -You can customize the PostgreSQL startup with three environment variables, which can also +You can customize startup of the PostgreSQL container with three environment variables, which can also be placed into a `.env` file: * `POSTGRES_USER` (default value: "postgres") * `POSTGRES_PASSWORD` (default value: "abcdefgh1!") * `POSTGRES_PORT` (default value: 5432) + +Note: if you choose different values here, then be sure to use those same values +in your [meadowlark-fastify](../../../services/meadowlark-fastify) `.env` file. diff --git a/README.md b/README.md index c7f9fe74..d812394e 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,26 @@ src="images/cropped-meadowlark-cc-by-nc-4.0-naturenerd_joel.png" align="right" width="300"> -The Meadowlark code and releases provide a deployable, distributable, -proof-of-concept for a cloud-native (i.e., built on cloud services) -implementation of the Ed-Fi API surface. It therefore replicates the data -collection capabilities of the Ed-Fi ODS/API, but does not replicate the -database structure and storage of the ODS/API. +Project Meadowlark is a research and development effort to explore potential for +use of new technologies, including managed cloud services, for starting up an +Ed-Fi compatible API. -:no_entry: Warning :no_entry:: this is not for use in production or -production-like settings. +While it was originally intended as a proof-of-concept, due to positive feedback +from the community, it is _beginning to align_ towards production readiness. +However, the current milestone 0.2.0 release leaves much work yet to be done +before there is a complete tool that could be used in a pilot test. Pilot +readiness is the current goal for milestone 0.3.0, which we hope to make +available before the Ed-Fi Summit in early November, 2022. + +See [Project Meadowlark - Exploring Next Generation +Technologies](https://techdocs.ed-fi.org/x/RwJqBw) in Tech Docs for more +information on the background and design decisions for this project. ## Getting Started -* [Deployment on AWS](docs/DEPLOYMENT.md) * [Running on Localhost](docs/LOCALHOST.md) -* [Developer getting started notes](docs/) - * [Additional technical details](docs/TECHNICAL.md) +* [Developer getting started notes](docs/README.md) +* [Additional technical details](docs/TECHNICAL.md) ## Legal Information diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md deleted file mode 100644 index f0345719..00000000 --- a/docs/DEPLOYMENT.md +++ /dev/null @@ -1,283 +0,0 @@ -# Meadowlark Remote Deployment - -| ❗ AWS remote deployment is temporarily unavailable, because we have removed DynamoDB and have not yet scripted any deploy process for either MongoDB or PostgreSQL. | -| -- | - -Meadowlark currently supports deployment to AWS. Remote deployment is performed -using the [Serverless Framework](https://www.serverless.com/framework/docs) -tool. Remote deployment does not require the local setup outlined in -[README.md](README.md). - -## Getting Started - -### Local Installation - -* Install [Node.js 14.x](https://nodejs.org/en/download/releases/) -* Install [Yarn 1.x](https://classic.yarnpkg.com/lang/en/) - -### Meadowlark API Security - -Note: Meadowlark API security is in the proof-of-concept stage. - -If you haven't already, copy the .env.example to a `.env.` file. For -example, if you wish to have a stage (aka environment) called "dev" in AWS: -`cp .env.example .env.dev`. There are two environment variables that deal with API -security. Both are used by an AWS deployment. - -* `SIGNING_KEY` is a hardcoded (that is, _fake_) OAuth 2.0 key. The example key - may be used for testing, or a new one can be generated via the `createKey` - endpoint. -* `ACCESS_TOKEN_REQUIRED` controls whether Meadowlark requires a valid OAuth 2.0 - access token in the `Authorization` header, and is enabled by setting it to - `true`. There are hardcoded credentials in the - [HardcodedCredential](packages/meadowlark/src/security/HardcodedCredential.ts) - file, and example generated tokens in the - [local.token.http](packages/meadowlark/test/http/local.token.http) file. - -### AWS Setup - -Set up your environment for command-line interaction with AWS. - -* Install the [AWS - CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) - if you don't already have it. -* Ensure that you have [configured your AWS CLI - enviroment](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) - with your security credentials. -* Ensure that your ["default" - profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) - is configured. This is the profile that Meadowlark will use. -* In the .env file, ensure that the lines for `AWS_ACCESS_KEY_ID` and - `AWS_SECRET_ACCESS_KEY` are commented out if they exist. -* Setting up the right policies in AWS is not our expertise. For non-administrative - users, we provided these policies, and they allowed developers to deploy out to AWS. - Please review carefully before applying in your account. - -**Policy: CreateMeadowlarkLambdaRole** -Replace "ACCOUNT_ID" below with your actual account id. - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "apigateway:GET", - "apigateway:PATCH", - "apigateway:POST", - "apigateway:PUT" - ], - "Resource": [ - "arn:aws:apigateway:us-east-1::/apis" - ] - }, - { - "Effect": "Allow", - "Action": [ - "apigateway:GET", - "apigateway:PATCH", - "apigateway:POST", - "apigateway:PUT" - ], - "Resource": [ - "arn:aws:apigateway:us-east-1::/apis/*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "cloudformation:CancelUpdateStack", - "cloudformation:ContinueUpdateRollback", - "cloudformation:CreateChangeSet", - "cloudformation:CreateStack", - "cloudformation:CreateUploadBucket", - "cloudformation:DeleteStack", - "cloudformation:Describe*", - "cloudformation:EstimateTemplateCost", - "cloudformation:ExecuteChangeSet", - "cloudformation:Get*", - "cloudformation:List*", - "cloudformation:UpdateStack", - "cloudformation:UpdateTerminationProtection" - ], - "Resource": "arn:aws:cloudformation:us-east-1:ACCOUNT_ID:stack/edfi-meadowlark-*/*" - }, - { - "Effect": "Allow", - "Action": [ - "cloudformation:ValidateTemplate" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "ec2:Describe*" - ], - "Resource": [ - "*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "events:Put*", - "events:Describe*", - "events:List*" - ], - "Resource": "arn:aws:events:us-east-1:ACCOUNT_ID:rule/edfi-meadowlark-*" - }, - { - "Effect": "Allow", - "Action": [ - "iam:AttachRolePolicy", - "iam:CreateRole", - "iam:DeleteRole", - "iam:DeleteRolePolicy", - "iam:DetachRolePolicy", - "iam:GetRole", - "iam:PassRole", - "iam:PutRolePolicy" - ], - "Resource": [ - "arn:aws:iam::*:role/edfi-meadowlark-*-lambdaRole" - ] - }, - { - "Effect": "Allow", - "Action": [ - "lambda:*" - ], - "Resource": [ - "arn:aws:lambda:*:*:function:edfi-meadowlark-*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "logs:DescribeLogGroups" - ], - "Resource": "arn:aws:logs:us-east-1:ACCOUNT_ID:log-group::log-stream:*" - }, - { - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:DescribeLogStreams", - "logs:FilterLogEvents" - ], - "Resource": "arn:aws:logs:us-east-1:ACCOUNT_ID:log-group:/aws/lambda/edfi-meadowlark-*:log-stream:*", - "Effect": "Allow" - }, - { - "Effect": "Allow", - "Action": [ - "s3:CreateBucket", - "s3:DeleteBucket", - "s3:DeleteBucketPolicy", - "s3:DeleteObject", - "s3:DeleteObjectVersion", - "s3:Get*", - "s3:List*", - "s3:PutBucketNotification", - "s3:PutBucketPolicy", - "s3:PutBucketTagging", - "s3:PutBucketWebsite", - "s3:PutEncryptionConfiguration", - "s3:PutObject" - ], - "Resource": [ - "arn:aws:s3:::edfi-meadowlark-*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "s3:*" - ], - "Resource": [ - "arn:aws:s3:::edfi-meadowlark-*/*" - ] - } - ] -} -``` - -### Deploy Meadowlark to AWS - -The Meadowlark [package.json](packages/meadowlark/package.json) includes scripts -that use the [Serverless Framework](https://www.serverless.com/framework/docs) -tool to deploy to a stage in your AWS environment. These scripts may be run with -`npm` or `yarn` from the `packages/meadowlark` directory. - -There are scripts to deploy to a "dev" stage and a "stg" stage. Note that -publishing to a stage overwrites any previous deployment to that stage. - -The first publish of a stage may take 10-15 minutes to complete. This is the -time it takes for AWS to provision a new OpenSearch instance, so please be -patient. Follow-on deployments to the same stage will be much faster. - -* Run `yarn deploy:aws dev` to deploy to the "dev" stage. -* Note the server portion of the endpoint URLs displayed at the end of the - deployment process for access to the deployed API. - -### Inspecting Meadowlark in AWS - -Your deployed Meadowlark stack can be inspected in your -[CloudFormation](https://console.aws.amazon.com/cloudformation/home) console in -the AWS UI. Ensure that you have selected the AWS region matching your AWS CLI -environment setup. Meadowlark stacks are named with an `edfi-meadowlark` prefix -followed by the stage name. For example, a "dev" stage deployment will be named -`edfi-meadowlark-dev`. See the [Cloudformation -documentation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-console-view-stack-data-resources.html) -for information on using the console. - -### Removing a Meadowlark Deployment - -A Meadowlark deployment includes a large number of AWS resources which incur -costs while deployed. While a Meadowlark deployment may be deleted from AWS like -any other Cloudformation stack, the easiest way to remove one is via -package.json script. - -* Run `yarn teardown:aws dev` to shut down running resources for the "dev" stage - and delete the published stack from AWS. - -### Load Ed-Fi Descriptors - -Meadowlark is packaged with the full set of Ed-Fi descriptors which, while not -required, must be loaded in order to use descriptor validation. - -* Run `yarn load:descriptors:aws dev` to invoke the Meadowlark Lambda function - that loads descriptors into the "dev" stage. Be patient, as this may take up - to 90 seconds to complete. - -### Test a Meadowlark AWS Deployment - -Meadowlark is bundled with test scripts to exercise the API. Similar to Postman, -these scripts are .http files that use the Visual Studio Code REST Client -extension. - -* Install [Visual Studio Code](https://code.visualstudio.com/) and the [REST - Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) - extension. In Visual Studio Code, open the Meadowlark-js folder. -* Open the - [remote.deployment.test.http](packages/meadowlark/test/http/remote.deployment.test.http) - file and update the @hostname entry to match your AWS deployment. -* Follow the directions to exercise your deployment with several API scenarios. - -### Deploy Meadowlark to a Different Stage - -The "dev" and "stg" stages are merely examples, and it is easy to deploy -Meadowlark to a different stage. An an example, for a stage named "prod": - -* Run `yarn build` first to ensure you are deploying the latest transpiled code. -* Run `yarn deploy:aws prod` - to deploy Meadowlark to the "prod" stage. -* Run `yarn teardown:aws prod` to remove Meadowlark from the "prod" stage. - -### Monitoring - -Logs are stored in CloudWatch, in the region used by the deployment. The default -configuration uses Virginia / US-EAST-1: [CloudWatch - Virginia - Filter on -Meadowlark](https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups$3FlogGroupNameFilter$3Dmeadowlark). diff --git a/docs/LOCALHOST.md b/docs/LOCALHOST.md index 73384537..3298749f 100644 --- a/docs/LOCALHOST.md +++ b/docs/LOCALHOST.md @@ -6,15 +6,25 @@ Instructions for running a local "developer" environment on localhost: 2. Enable [Yarn](https://yarnpkg.com/getting-started/install) as the package manager 3. Install [Docker Desktop](https://www.docker.com) 4. Review the [General Docker Guidance](../Meadowlark-js/docker/using-docker.md) for Meadowlark -5. Initialize a backend technology: - * **MongoDB**: Start a MongoDB cluster in Docker - ([instructions](../Meadowlark-js/backends/meadowlark-mongodb-backend/docker/readme.md)). - * **PostgreSQL**: Start PostgreSQL in Docker ([instructions](../Meadowlark-js/backends/meadowlark-postgresql-backend/docker/readme.md)). -6. Initialize OpenSearch by starting it up in Docker - ([instructions](../Meadowlark-js/backends/meadowlark-opensearch-backend/docker/readme.md)). -7. Setup environment variables for running [meadowlark-fastify](../Meadowlark-js/services/meadowlark-fastify/) - service. The folder has an example.env file with all settings needed to run the service, the easiest way to set your environment variables is to duplicate this file in the folder and rename the file to .env - 1. Review the settings in the .env file to see what values, if any, to change. In particular: - * DOCUMENT_STORE_PLUGIN - Make sure your chosen backend is uncommented and comment out the rest - * QUERY_HANDLER_PLUGIN - Having this value uncommented allows for querying from Opensearch - * LISTENER1_PLUGIN - Having this value uncommented will allow Meadowlark documents to flow to OpenSearch +5. The Meadowlark runtime currently requires running either PostgreSQL or + MongoDB as the primary datastore, and OpenSearch as a secondary storage for + high-performance queries. Before running the Meadowlark code, startup local + instances of the data stores that you wish to use. The repository comes with + Docker compose files for easily starting up all three. Either run + `eng/docker.ps1` in PowerShell to start all three data stores at the same + time (using default configuration), or see the individual directories if you + wish to customize or to run `docker compose` directly in the directory + containing the compose file: + * [MongoDB](../Meadowlark-js/backends/meadowlark-mongodb-backend/docker) + * [PostgreSQL](../Meadowlark-js/backends/meadowlark-postgresql-backend/docker) + * [OpenSearch](../Meadowlark-js/backends/meadowlark-opensearch-backend/docker) +6. Setup environment variables for running + [meadowlark-fastify](../Meadowlark-js/services/meadowlark-fastify/) service. + The folder has an `example.env` file with all settings needed to run the + service; the easiest way to set your environment variables is to duplicate + this file in the folder and rename the file to `.env`. Review the settings in + the `.env` file to see what values, if any, to change. In particular: + * `DOCUMENT_STORE_PLUGIN` - Make sure your chosen backend is uncommented + and comment out any others. + * `QUERY_HANDLER_PLUGIN` and `LISTENER1_PLUGIN` - Uncomment these two for + GET query support using OpenSearch. diff --git a/docs/README.md b/docs/README.md index 8cfbabbc..254e7e33 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,8 +2,14 @@ These are the instructions for setting up a local environment for Meadowlark development and testing. These instructions are independent of remote -deployment. For remote deployment instructions, please see -[DEPLOYMENT.md](DEPLOYMENT.md). +deployment. + +## Required Toolkit + +* NodeJs 16 +* Yarn 3 +* Docker Desktop or equivalent +* Visual Studio Code (recommended) ## Repo structure @@ -53,9 +59,13 @@ Meadowlark is packaged with the full set of Ed-Fi descriptors which, while not required, must be loaded in order for descriptors to validate successfully. * Run `yarn start:local` in one shell. -* In a second shell, cd into /Meadowlark/eng and the run Invoke-Bulkload.ps1 script - * Note: This requires building the [Bulk Load Client Utility](https://techdocs.ed-fi.org/display/ODSAPIS3V53/Bulk+Load+Client+Utility) -* If using AWS-lambda as your service layer, you can also run `yarn load:descriptors:local` from the /Meadowlark-js/services/meadowlark-aws-lambda folder to load descriptors +* In a second shell, cd into /Meadowlark/eng and the run Invoke-Bulkload.ps1 + script + * Note: This requires building the [Bulk Load Client + Utility](https://techdocs.ed-fi.org/display/ODSAPIS3V53/Bulk+Load+Client+Utility) +* If using AWS-lambda as your service layer, you can also run `yarn + load:descriptors:local` from the /Meadowlark-js/services/meadowlark-aws-lambda + folder to load descriptors ## Other Build Scripts diff --git a/docs/TECHNICAL.md b/docs/TECHNICAL.md index 065c3580..adeac780 100644 --- a/docs/TECHNICAL.md +++ b/docs/TECHNICAL.md @@ -1,73 +1,103 @@ # Meadowlark -## Resource Ids +For see [Project Meadowlark - Exploring Next Generation +Technologies](https://techdocs.ed-fi.org/x/RwJqBw) in Tech Docs more information +on the Meadowlark architecture. -Resource Ids are calculated from the fully qualified MetaEd entity type (project name, project version, entity name) and -natural key string. They are a 224 bit hash using SHA-3's SHAKE-128 algorithm as implemented by the jsSHA library. +## Document Identifiers -The entity type string is of the form "TYPE#\#$\#\". The natural -key string is of the form "\", where pairs are of the form "\=\" and pairs are in alphabetical order. +Each document posted to the Meadowlark API is assigned a unique Document +Identifier formed by calculating a SHA-3 SHAKE-256 hash value from two +components, and then concatenating the result. These identifiers are +deterministic and repeatable across nodes in a cluster or disparate +installations. This unique identifier then becomes the "primary key" for data +store lookups of a single object, and it is used in the query string for HTTP +requests to a particular document. -Document Ids are 56 character (224 bit) hex strings. For example: 6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7 +The first component in the identifier the data model name and resource type, +separated by `#`. Example: `Ed-Fi#Student`. The second component is a key-value +pair representing the natural key components as defined by the Ed-Fi Data +Standard. Example: `{"studentUniqueId":"abc"}`. These are then combined as: + +```none +hash(Ed-Fi#Student) + hash({"studentUniqueId":"abc"}) +``` + +The hash values are further base 64 encoded for string storage, leading to a +38-character string. For example: `WhT14ozRv-M80122NydSim1PK4FiQIxmPRXMTA` +corresponds to the student document described above. ## Authentication -Due to time constraints, the implementation contains a simple test harness with support for a few hard-coded key/secret -combinations. These key/secret pairs will support OAuth2 client credentials flow and create signed JSON Web Tokens (JWT) that -encode claim information. The claim information can then be used for authorization and record ownership. Please note that the -protection is incomplete; for example, it does not verify the `aud` in the token and the token won't expire until sometime in -the year 2091. It does, however, verify the signing key. +Due to time constraints, the implementation contains a simple test harness with +support for a few hard-coded key/secret combinations. These key/secret pairs +will support OAuth2 client credentials flow and create signed JSON Web Tokens +(JWT) that encode claim information. The claim information can then be used for +authorization and record ownership. Please note that the protection is +incomplete; for example, it does not verify the `aud` . It does verify the +signing key. * Default token endpoint: `/{stage}/api/oauth/token` -* Verification endpoint (useful for debugging): `GET /{stage}/verify` with authorization header -* Create a random key for signing: `GET /stage/createKey` +* Verification endpoint (useful for debugging): `GET /{stage}/verify` with + authorization header +* Create a random key for signing: `GET /stage/createKey` * Place in the Meadowlark `.env` file as SIGNING_KEY . * Name of the vendor is stored in the subject. * There are two hard-code key/secret pairs for different "vendors": - | Key | Secret | - | --- | ------ | + | Key | Secret | + | ----------------- | ------------------- | | ​meadowlark_key_1 | meadowlark_secret_1 | - | meadowlark_key_2 | meadowlark_secret_2 | + | meadowlark_key_2 | meadowlark_secret_2 | -# MetaEd to ODS/API Surface +## MetaEd to ODS/API Surface -While Ed-Fi models are described using the MetaEd language, their most common expression is as the API surface of an -ODS/API implementation. The mapping between MetaEd models and their API surface expression is largely straightforward, -but there are some important differences. +While Ed-Fi models are described using the MetaEd language, their most common +expression is as the API surface of an ODS/API implementation. The mapping +between MetaEd models and their API surface expression is largely +straightforward, but there are some important differences. -## Element Naming +### Element Naming -### Casing +#### Casing -Names on the ODS/API surface are always lower camel cased, whereas names in MetaEd models are always upper camel cased. +Names on the ODS/API surface are always lower camel cased, whereas names in +MetaEd models are always upper camel cased. -### Simple Names +#### Simple Names -For the most part, the names of individual elements are the same between the MetaEd model and ODS/API surface. -MetaEd "role names" are expressed as prefixes on the property name. +For the most part, the names of individual elements are the same between the +MetaEd model and ODS/API surface. MetaEd "role names" are expressed as prefixes +on the property name. -### Name Overlap Collasping +#### Name Overlap Collasping -The ODS/API surface has naming rules that remove overlapping prefixes of properties that match the parent entity name in some cases. As a simple example, a MetaEd property "XyzAbcd" on an entity "Xyz" may be expressed in the API as "Abcd". However, the rules for name collapsing can get quite involved in complex cases, and is beyond the scope of this document. +The ODS/API surface has naming rules that remove overlapping prefixes of +properties that match the parent entity name in some cases. As a simple example, +a MetaEd property "XyzAbcd" on an entity "Xyz" may be expressed in the API as +"Abcd". However, the rules for name collapsing can get quite involved in complex +cases, and is beyond the scope of this document. -## References +### References -In general, a reference on the ODS/API surface is made up of the set of individual natural key values that specify -the referenced entity. This set is wrapped by an object named "xyzReference", where "xyz" is the singularized name of the -entity being referred to. +In general, a reference on the ODS/API surface is made up of the set of +individual natural key values that specify the referenced entity. This set is +wrapped by an object named "xyzReference", where "xyz" is the singularized name +of the entity being referred to. -### Reference Collections +#### Reference Collections -Reference collections are arrays of single item references where the array is named as the pluralized name of the entity -being referred to. +Reference collections are arrays of single item references where the array is +named as the pluralized name of the entity being referred to. -### Descriptors +#### Descriptors -References to descriptors are suffixed by "Descriptor" instead of "Reference" like all other entity references. +References to descriptors are suffixed by "Descriptor" instead of "Reference" +like all other entity references. -## Choice and Inline Common +### Choice and Inline Common -There are no direct equivalents to MetaEd Choice and Inline Common entities in the ODS/API. Instead, they are considered -more like bags of properties which are pulled up to the same level as the entity with the Choice/Inline Common reference. -Since references to Choice/Inline Common can be nested, the pull-up can happen from multiple levels. \ No newline at end of file +There are no direct equivalents to MetaEd Choice and Inline Common entities in +the ODS/API. Instead, they are considered more like bags of properties which are +pulled up to the same level as the entity with the Choice/Inline Common +reference. Since references to Choice/Inline Common can be nested, the pull-up +can happen from multiple levels.