Service basics

This article provides an overview of the elements related to your technical documentation which all lives in the Service editor in Uniscale.

Introduction to services

In Uniscale, services are created for two possible uses. For a solution or as a standalone service.

  • Solution-owned-services are, as the name suggests, services created only to provide functionality within the bounded context of a solution. Their functionality is exposed via revisions to all the modules inside the owning solution.

  • Standalone services are the specification of an individual service without any links to its surrounding eco-system.


Service Composition: Understanding the Key Elements

Services are multifaceted, incorporating various elements that work cohesively to deliver functionality. This article delves into the primary components involved in service design, their application, objectives, and underlying principles. By dissecting these elements, we aim to provide a clearer understanding of what goes into creating a robust service.

// Example of a service with various levels of namespaces 
// and functionality
Service 
├─ Namespace
|   └─ Endpoint A
|   └─ Endpoint B
|   └─ Technical Usecase 
|      └─ Technical Usecase Flow 
└─ Empty namespace
└─ Namespace
   ├─ Aggregate A
   ├─ Aggregate B
   └─ Value object
   └─ Namespace
      └─ Property group

Namespaces

Understanding Namespaces in Your Service Architecture

Namespaces play a crucial role in the hierarchical organization of your service. Initially, they function to provide a structured framework. However, as your library evolves, namespaces become pivotal for identification purposes.

Because we want to enable you to express your domain you should make sure that the hierarchy and naming of your namespace reflect the problem domain you intend to fulfill through this service.

Examples of namespace structure for a service:

Account (root level)
├─ Users
|   └─ Actions
└─ Sessions
   ├─ Activity
   └─ Backlog sessions

Revision cycle & update cycle

Namespaces are subject to the locking/unlocking/readying process within a revision. This means that editors of a service can manage the content via each of those statuses as needed.

The name of a namespace is strictly managed through locking and unlocking while the description is easily editable. The reason is that the namespace forms the location of endpoints and data contracts all the way into the SDK so changing a namespace name structurally changes the location of things in the SDK.

Use cases

Use cases (UC) come in two forms Functional and technical.

Functional UCs paint a more general picture of how a user might interact with your business to reach their goals. Instead of focusing on technical detail, it’s a cause-and-effect description of different inputs.

This type of UC is encountered and created from inside the specification.

In Uniscale, the main differentiation factor for the UCs is their technical intention.

The difference is Functional use cases are defined in and owned by the module as it is describing end-user functionality. The functional use cases can be connected to either standalone or technical use case flows. However, only standalone and technical use case flows are defined within the Service To Module.

Technical use cases

The technical use case does not describe how the end user interacts. It rather is a group of flows that the services expose as a larger group of functionality that belongs together. For instance, a use case for a service could be mail-sending management where it exposes flows for sending mail, checking the status of sent emails, and for instance hourly stats.

It does not describe the end-user functionality, that will always be described through a module but it describes a larger set of technical functionality that the service wants to expose to its users (developers for other services/frontends).

Revision cycle & update cycle

UCs are subject to the locking/unlocking/readying process within a revision. This means that editors of a service can manage the content via each of those statuses as needed.


Use case flows

Different types of flows (technical/standalone) form the specification of endpoints. They represent the intention of use of an endpoint and if an endpoint is not linked into at least one flow it will not become part of the SDK as it has no intention of use.

Standalone use case flow

Use case flows (UCF), as the name suggests, can be used in one or more functional use cases if service to a module or as a single standalone flow against another service (if service to service)

A functional UCF contains:

  • a title

  • a Rich Text description

  • Possible functional Acceptance criteria

  • and a list of linked endpoints

Technical use case flows

They can have any technical intention, as they are a clearly defined larger use case that is owned by the service itself.

A technical UCF contains:

  • a title

  • a Rich Text description

  • possible Technical Acceptance criteria

  • and a list of linked endpoints

Revision cycle & update cycle

UCFs are subject to the locking/unlocking/readying process within a revision. This means that editors of a service can manage the content via each of those statuses as needed.


Endpoints

In Uniscale, we follow the same understanding and concept around endpoints as the industry.

We must first distinguish between APIs and Endpoints as those can be quite familiar terms, with slight differences though.

Endpoint vs. API

It’s important to note that endpoints and APIs are different.

An endpoint is a component of an API, while an API is a set of rules that allow two applications to share resources.

An Endpoint is the definition, structure, and traits of a service's exposed functionality. An API is the concrete implementation of that Endpoint determining location and means of transport. An endpoint is abstract API is concrete.

For example, an Endpoint exposed over HTTP is an HTTP API. An endpoint handled in a Uniscale Interceptor is a Uniscale API


As such, coming back to the definition of endpoints, they represent the configuration of the expected behavior of the API, together with the expected payloads for the possible request and response. Quite a bit to take in so let's break that down.

Configuration of the expected behavior of the API

An endpoint will be defined by:

  • behavior type: message or Request response

  • owning namespace (location in SDK structure)

  • behavior ( create, read, etc)

  • name ( predefined based on behavior or custom )

  • description optional

Payloads

Depending on the type, an endpoint can contain one ( Message) or two payloads (Request - Response):

Error codes

Once your endpoints are configured and set up with their payloads, they can be enriched with more configuration on how they will interact with outside systems or requests.

Initially, every endpoint assumes a happy scenario where no complications or bad scenarios can occur. The reality is different, of course, and for various reasons, your frontend can present various scenarios where the data is wrong, or missing, or the backend cannot process the requested information.

In Uniscale you can enrich each endoints with direct Error codes to cover those exact scenarios. For each error code, you can provide:

  • a system code

  • a user message

If you use the Uniscale session and interceptors the error response management is automatically handled through call chains. This means a frontend calling a service, calling another service.

Data contracts

Structuring your data can be done in multiple ways in Uniscale, and as such we have provided various top-level "containers" or "contracts" to manage their scope, and bounded context, in this way facilitating easier management of their Single source of truth.

Revision cycle & update cycle

Data contracts are subject to the completion process within a revision. This means that editors of a service can manage the content via each of those statuses as needed.


Aggregates

Within DDD, aggregates represent the main data containers for your properties and data modeling. They are mainly characterized by a Single source of truth for individual properties and act as an umbrella for all the values within the same concept. The main definition of an Aggregate is that it is data that is referenceable by an identifier.

Aggregates are unique by identifier, which is automatically generated based on the aggregate name. As such checking if two aggregates are equal is done by comparing their identifiers.

Aggregates are placed under a namespace which acts as its owner.

Example of aggregates:

  • User

  • Person

  • Order

In Uniscale, aggregates have by default an aggregate identifier created automatically which follows the aggregate name. Afterward, an aggregate can be expanded with a flat or nested structure of properties.


Value objects

Value objects represent data contracts that are slightly different from aggregates. They are unique in content and as such they can be quite simple in structure or even standalone.

Value objects are placed under a namespace which acts as its owner.

Example of value objects:

  • Address

  • SearchPayload

There are 2 types of value objects

  • type alias: a type alias is a native type with a specific meaning (ex. Gender). For instance, if an endpoint wants to use a native type as its input/output it must declare a type alias value object and use that.

  • type structure: a nested structure of properties (ex. SearchPayload). This is encountered in situations where the request of an endpoint is a fixed structure that is always the same but it is not necessarily an aggregate.

Value objects can be referred to as other aggregates, value objects, and property groups. Editing them anywhere in the interface will also update the source of truth at its origin.


Property groups

Once further into your modeling, you will reuse certain properties from across various aggregates and value objects, also with different cardinalities in mind. Exploring such scenarios, we can have:

Example:

Property groups can be explained as a way of composing data from different value objects and aggregates to be used in Endpoint payloads. The only place and main place to use property groups is as a payload for endpoints, being in a Message or a Request-response situation.

Native type system

A native data type is a classification of data that tells the library generator how the library intends to use the data. In Uniscale we provide all the most commonly used data types. They are:

aNote: UTF is used as a variable-length character encoding standard.

Technical intentions

At its baseline, every functionality in a service should exist for a purpose and intent. That intent can be internal or external. Towards functionality (solution or module), or another system ( service or infrastructure)

Service to module

All the functionality a service provides means to be exposed to a Frontend, a module, or a solution.

Service to service

All the functionality a service provides means to be exposed to a system, in the shape of another service can sometimes be referred to as Backend communicating with another backend.

Service to infrastructure

All the functionality a service provides means to expose an infrastructure as a whole, usually Storage, Networking, or any other IaaS scenarios.

Only standalone use case flows and technical use cases in a service are governed by technical intentions.

Last updated