Understanding Service Types, Service Objects and Architecture
Think of SmartObjects as an abstracted data access layer that exposes data and operations from data providers (such as web services, data stores or REST endpoints) to applications running in K2. SmartObjects are essentially a “middle layer” between sources of data and applications that require the data. K2 provides a number of standard connectors (known as "Service Types" or "service brokers") that can expose common Line-of-Business (LOB) systems as SmartObjects. However, it is possible to extend the standard set of Service Types with custom Service Types that integrate with other LOB systems. In K2, SmartObjects are the preferred mechanism to integrate your applications with LOB systems and therefore it is quite common to write custom Service Types to connect to other technologies not supported by K2 out-of-the-box.
SmartObjects act as a translator (or “adapter”) which takes the native entities and operations of the LOB system and converts the data and methods into an easy-to-understand, consistent interface that the consuming applications can understand. Internally, this is achieved through the use of technology-specific "connectors" called Service Types that expose Service Objects. Designers can then create SmartObjects that refer to one or more Service Objects, and then use those SmartObjects in their applications.
SmartObjects are executed by the server. Essentially, the server acts as the "engine" that will query the provider through the Service Type when a SmartObject is executed. While you can certainly use or "call" SmartObjects in many consuming applications, it is not the application itself that does the actual processing to retrieve the data from the underlying system: that task is performed by the server. Furthermore, the server does not cache SmartObject data; K2 will always retrieve the latest data from the provider whenever the SmartObject is executed.
Custom Service Types are implemented by creating a JavaScript (JS) library. The library would implement all the necessary logic/references to expose the provider's native objects and operations as Service Objects, and handles all interaction with the provider. Once the library is written and built, you would host the resulting JS file in an accessible location or upload it to the server, and then use Management to register a Service Type. Once the Service Type is registered, you can create one or more Service Instances for the Service Type, and then designers can create SmartObjects that use the Service Objects. To learn about the process of creating and using custom Service Types in K2, see the topic Introduction to the JavaScript Service Provider
Before creating custom Service Types, it is very important that you understand the terms that are used in the SmartObject architecture, how the components work together and the role of each component.
The diagram below illustrates the conceptual architecture of SmartObjects and the JSSP. Pay special attention to the red, italic text: these are the terms you need to understand while developing a custom service type. We describe each term in more detail following the diagram.
JSSP Architecture
Let’s take our sample diagram and break down the SmartObjects components:
Service Type
A Service Type is a TypeScript JavaScript file that contains all the code necessary to interact with a specific provider. Typically, this file contains the code required to interact with the provider's APIs and it would contain code that discovers and/or describes the provider's objects to K2 in a consistent way as Service Objects. In some circumstances, the Service Type may also implement logic to handle security for the provider's API. Essentially, the Service Type's task is to "translate" the provider's native objects into Service Objects (and vice-versa), and to call the various methods in the Provider's API at runtime. Service Types are normally written to expose a particular technology. In our example diagram, we have a Service Type that understands how to interact with a hypothetical service that provides Employee and Account data.
Since the Service Type contains all the code necessary to interact with a provider, this is the only item a developer needs to implement when they want to expose some other system as K2 SmartObjects. As long as the Service Type conforms to the outline and base methods described in the Understanding JavaScript Service Provider project structure topic, the components in the architecture can load the necessary functionality.
In addition to the logic required to interact with the provider's APIs and describing the Service Objects, the Service Type can also define configuration settings (known as Service Keys) that allow the administrator to configure the Service Instance when they register a Service Instance. Typically, these configuration keys define values like URLs to reach the provider's APIs, or other configuration values that may differ between instances of the same Service Type.
Service Type files reside in a location accessible to the server (such as a GitHub RAW URL), or you can upload the Service Type file to the server when you register the Service Type with K2.
You must register the Service Type with a K2 environment before you can start adding Service Instances of that type.
Service Instance
Once a Service Type is registered in the K2 environment, Administrators can create one or more Service Instances for that Service Type. A Service Instance is essentially a configured instance of a Service Type, and contains various configuration values that are required by the Service Type. The configuration values could include values required to connect to the data provider (for example, a server name or a URL) and perhaps additional configuration values that describe how the service type should behave. Another important setting for a Service Instance is the Authentication Mode setting: this determines the user credentials that are passed to the underlying Service Type at runtime.
In the diagram, there is one Service Instance for the sample Service Type. You could, however, have more than one instance of the same Service Type. For example, if you had a Service Type that exposes PostgreSQL databases to K2, you might have two separate Instances of the Service Type, each configured to connect to a different PostgreSQL database.
When you register a Service Instance, K2 executes the ondescribe method that describes the available Service Objects for the targeted Service Instance.
Service Object
Service Objects are essentially "translated" representations of the entities in the underlying data provider. Under the covers, Service Objects are just representations of the entities in the provider and contain no processing logic.
Service Objects expose the properties and methods for objects in a Provider as consistent SmartObject Method Types and Property Types. It is the responsibility of the code in the Service Type to perform the necessary mapping between the Provider's types and the types used by SmartObjects. You can define Service Objects statically or discover them dynamically.
Service Objects are always tied to a Service Instance, because the Service Instance exposes the Schema that describes the Service Object. Note that Service Objects are defined by the Service Type and cannot be consumed in applications directly. Designers must create SmartObjects that are tied to one or more Service Objects before the service can be consumed in an application.
A Service Instance would usually contain one or more Service Objects. As part of the translation, the Service Type will convert the objects in the provider to Service Objects; the properties of the objects are converted to Service Object Properties and the methods in the provider's objects are converted to Service Object Methods. A large part of implementing a customer Service Type is to write the code that will perform this "translation" to represent the underlying system's artifacts as Service Objects.
In our sample diagram, we have two Service Objects (soEmployee and soAccount), each with two preoprties and two methods. Ultimately, these Service Objects represent the entities in the Provider's API (poEmployee and poAccount).
SmartObject
The final step to integrate a data provider with applications is to create SmartObjects that reference one or more Service Objects. This task can be performed by K2 Administrators by automatically generating SmartObjects when a Service Instance is registered, or by Designers that use the Designer to create more advanced SmartObjects that interact with Service Objects. Eventually, these SmartObjects are used in the consuming applications. Note that the consuming application does not need to know anything about the underlying provider to use the SmartObject.
When a SmartObject is executed at runtime, K2 calls the Service Type's onexecute method, passing in the name of the Service Object and which method was called, along with any method parameters and properties. The Service Type code will then typically direct processing to a helper method based on which Service Object and which Service OBject Method was called.
When you are writing a custom JSSP Service Type, you will need to decide between statically describing the Service Objects in the Service Type, or dynamically discovering the Service Objects in the Service Type.
Static Service Objects are used when the underlying schema of the provider does not change frequently. For example, you may have a service that returns currency information, and the service always returns the same objects, properties and operations. You can use the static approach here because the schema of the data and operations exposed by the service is always the same, regardless of where the service is located. Every Service Instance for that Service Type would expose the same Service Objects with the same properties and methods. Since the schema of currency web service never changes, we can create a static Service Type that describes the currency service's objects as Service Objects. Essentially, the description of the Service Objects is hard-coded into the Service Broker code.
Dynamic Service Objects are useful when you want to create a re-usable Service Type that can expose different instances of the same technology, but the instances may have different schemas. These Service Types are dynamic because the Service Type will discover the schema of the targeted provider when a Service Instance of the Service Type is registered or refreshed. Here is an example: suppose you wanted to create a Service Type that can interact with a SQL-like database such as PostgreSQL. The Service Type will always interact with the same technology (PostgreSQL), but you cannot predict what the underlying schema of a particular instance of a PostgreSQL database will look like because the tables and stored procedures will differ between instances of PostgreSQL. You therefore need to use the dynamic approach that will query a particular instance of a PostgreSQL database when a Service Instance is registered or refreshed. The discovery process will use some kind of query to determine the various tables and procedures available in that PostgreSQL database, and then describe these as Service Objects.
The topic ondescribe shows examples of how each approach works.