Architecture / API

Overview

As of version 2015.1, Engine provides a RESTful API for interacting with core Engine functionality. The documentation for the API should have been provided along with your release here. Many programming languages already provide extensive support for interfacing with RESTful APIs, and so it is certainly possible that your application can generate API calls itself. For your convenience, however, we have also generated two API clients: one for .NET, and another for Java. The clients should have been included in your initial deliverable. The clients will allow you to work directly with the API from your application without having to create API bindings yourself.

Note that all API calls require choosing a "tenant". For more information on what tenants are and how to use them, consult the "Tenancy" section, below.

Note also that there are many other API resources and optional features that are not covered in the following documentation. This documentation provides only a brief overview. If you ever have further questions about how to do something specific, do not hesitate to inquire with your assigned integration engineer or to contact us.

Tenancy

"Tenancy" refers to the ability of a single instance of a running web application to serve multiple groups of users ("tenants") as if they were interacting with their own separate instance of the application, all while keeping the the data that belongs to these tenants separate.

For example, an LMS might have several organizations as customers, each of whom have their own set of users, packages, and registrations.

A multi-tenant architecture allows each of these organizations (tenants) to harness the full power of the LMS without the overhead of running a separate server with a separate instance of the LMS for each organization. Engine is designed to work with your application's tenancy model.

Whether your application separates different tenants into different databases or keeps all tenants together in the same database, Engine is capable of being configured to support it and to enforce secure separation of tenant data.

Implementation

The concept of tenancy is built in to Engine at a very low level; every single row in every single one of Engine's tables has a column that identifies the tenant to which the data belongs. Furthermore, run-time checks enforce data access rules for every single query, so you can be assured that one tenant will not be able to access another tenant's data. Among other things, this means you can use a single database instance to serve all of your tenants.

Engine identifies tenants by a case-insensitive name, consisting of 64 or fewer characters. (Internationalized characters are allowed, but you will have to take care to escape them when using the tenant names in URIs.) All API calls include (indeed, require) the tenant name as the first element of the API path. For example, to create a registration, you can POST to the registrations resource, /{tenant}/registrations. If you wish to create a registration for your "AcmeCo" tenant, you make POST calls to http://example.org/ScormEngineInterface/api/v1/acmeco/registrations, and if you want to create a registration for your "BusinessPlus" tenant, you make a POST call to http://example.org/ScormEngineInterface/api/v1/businessplus/registrations.

Tenants are created on demand; in order to create a new tenant, all you need to do is make a new API call that uses that tenant. You can restrict this behavior by specifying which tenant names are valid in your configuration file. You can use the AllowedTenantNames configuration setting to enumerate explicitly which tenants can be created, and you can use the AllowedTenantNameRegex configuration setting to specify a range of valid tenant names.

Note that the tenant name is required in all API calls. If you are not using tenancy, you will simply use "default" as the tenant name in all API calls.

xAPI Tenancy

Experience API (xAPI, formerly "Tin Can API") calls are formatted slightly different from Engine's API (as the xAPI structure is governed by a third-party standard). To use tenancy with xAPI, you must include the tenant name as part of the endpoint URL. By default, all you need to do is add the tenant name as the next path element between the base endpoint URL and the endpoint method. For example, the xAPI statements endpoint for "AcmeCo" might be http://example.org/ScormEngineInterface/TCAPI/acmeco/statements, while the xAPI activities endpoint for "BusinessPlus" might be http://example.org/ScormEngineInterface/TCAPI/businessplus/activities.

The tenant name is not required for xAPI calls, but if it is not included, the endpoint will always reference the default tenant. It is strongly recommended that customers using tenancy always specify the tenant when composing xAPI endpoint URLs!

All of the preceding works for Recipes as well, except instead of coming between TCAPI and the method name, the tenant name will come between the Recipes dispatch path and the method name. For example, the xAPI interactions data for the "MaximumProfit" tenant might be accessed at something like http://example.org/ScormEngineInterface/Recipes/interactions/maximumprofit/results. The default tenant can be accessed by using "default" as the tenant name or by leaving out the tenant name entirely.

Database Considerations

By default, Engine assumes that you are using one database. As discussed earlier, Engine's tenancy is built-in at a very low level; every database row will include information identifying the tenant to whom the data pertains.

Clearly, keeping all Engine's data together in the same place is the simplest way to set up Engine, and we strongly recommend it. But for technical reasons, some customers may want to use multiple databases to support tenancy. Engine supports this, but care must be taken in the initial set-up of Engine.

Engine requires the use of two database schemas. First, there is the system schema, which contains information that is shared between all of Engine's tenants. This includes the catalog of tenants themselves, as well as some tables related to configuration settings. There can never be more than one system schema (nor should there need to be). The connection string for system tables is governed by the SystemDatabaseConnectionString configuration setting.

Second, there is the tenant schema, which contains all tenanted information. If your application needs to use different databases for different tenants, this is the schema that needs to be duplicated. The connection string for these tables is governed by the TenantDatabaseConnectionString configuration setting. (For information on how to change this setting on a per-tenant basis, see the "Configuration" section, below. If the setting is not given, SystemDatabaseConnectionString will be used instead.)

For single-database customers, it is possible (and encouraged!) to combine the system and tenant schemas into a single database (by just providing the same connection string for both the system and tenant schemas). For customers who are going to try to split tenant data accross multiple databases, however, the system and tenant schemas need to be kept separate.

The most common reason to split tenant data accross several databases is to reduce the size of the data contained on any particular database instance. Many modern DBMSes provide (either themselves, or with the help of third-party tools) mechanisms for sharding a database so a logically single database can be divided among smaller "shards". How this is accomplished is beyond the scope of this documentation. We pause here only to say that customers wishing to do this will need to base their sharding scheme on the tenant_id column, which is contained in every table on the tenant schema. Since your DBMS is handling sharding on your behalf, you will not need to use multiple configuration files.

Alternatively, it may be impossible to use sharding tools to automatically separate the data from different tenants into multiple shards for technical reasons. For example, your application may come bundled with other tables that need to be re-created on a per-tenant basis. In order to make Engine work, you will need to create the multiple instances of the tenant schema yourself. You will also need to use multiple configuration settings (see below) to specify the connection string for each tenant.

Configuration

Many configuration settings can be specified on a per-tenant basis. When getting a tenant-specific configuration setting, Engine will check for the setting prefixed by the lower-case tenant name and a dot. If found, the value of that entry will be used; if not, the value of the non-prefixed setting will be used.

For example, your LMS might have 5 tenants: AcmeCo, BusinessPlus, MaximumProfit, MoneyCorp, and SolutionsInc. If the first three use connection string database=a;password=secret; and the last two use connection string database=b;password=secret, then you can include the following two entries in your configuration file:

<add key="DataPersistenceEngine" value="sqlserver"/>
<add key="SystemDatabaseConnectionString" value="database=system;password=secret;"/>
<add key="TenantDatabaseConnectionString" value="database=a;password=secret;"/>
<add key="moneycorp.TenantDatabaseConnectionString" value="database=b;password=secret;"/>
<add key="solutionsinc.TenantDatabaseConnectionString" value="database=b;password=secret;"/>

Alternatively, you may use the ${TenantName} token for settings which you want to vary by tenant, but the only difference is the tenant name. For example, you could keep each tenant's content under a different path:

<add key="WebPathToContentRoot" value="/courses/${TenantName}">
<add key="FilePathToContentRoot" value="C:\inetpub\wwwroot\courses\${TenantName}"/>

These examples use the .NET configuration file format, but the principle still holds for Java-style configuration.

The tenantname.<setting> notation and ${TenantName} token replacement are currently limited to a subset of the available configuration settings. We will continue to enable this syntax for more settings in future releases. The current list of supported settings is:

TenantDatabaseConnectionString, WebPathToContentRoot, FilePathToContentRoot, FilePathToUploadedZippedPackage, ApiRollupRegistration*, and RedirectOnExitUrl