Skip to content

Development of Flexible API Layer #4275

@akash5100

Description

@akash5100

Creating a Flexible API layer in the contentcuration app in Studio

Overview

Within the contentcuration app in Studio, we want to build an API layer that acts as a communication bridge with different backends like Docker Images, Google Cloud Platform's Vertex AI, and VM instances, cloud storage services, etc. The goal is to make sure this API layer can work with these backends, regardless of where or how they do the job. As long as the input and output formats stay the same, this setup provides flexibility in choosing and using backend resources.

Description and outcomes

The stand-alone deployed backend service(s) will not have direct access to contentcuration models or the database for that matter, so this API layer facilitates access to these resources by receiving and returning a standardized requests and responses, irrespective of the backend interacted with.

The Architecture

Screenshot 2023-09-11 at 14 50 06

The main components of this API layer are

  1. Backend Interface
  2. Adapter

Creating the Backend Interface

The core Backend class will be an abstract interface that defines the operations that all backends must support. It also implements the Singleton pattern, and provides a make_request method to forward requests to the chosen Backend, whose formats can be specified using the request and response methods.

ABSTRACT CLASS Backend:
	_instance = None # Private variable to hold the instance

	ABSTRACT METHOD connect()
		# Provides blue print to connect
		pass

	ABSTRACT METHOD make_request(params)
		# provide blue print to make request
		pass

        ABSTRACT METHOD request(params)
		# provide blue print for the request object
		pass
	
	ABSTRACT METHOD response(params)
		# provides blue print for the response object
		pass

	CLASS METHOD get_instance(cls)
		IF cls._instance is None:
			cls._instance = cls._create_instance()
		return cls._instance

	CLASS METHOD _create_instance(cls)
		raise NotImplementedError # concrete class must implement

Different backends can now be created by implementing the base Backend class:

# Implement CONCRETE CLASS using ABSTRACT Backend class
CLASS GCS IMPLEMENTS Backend:
	METHOD make_request(params):
		# make request to Google Cloud Storage services

	METHOD connect(params):
		# Implement the connect method for GCS

	CLASS METHOD _create_instance(cls)
		# initialize a GCS Backend instance

CLASS ML IMPLEMENTS Backend:
	METHOD make_request(params):
		# make request to DeepLearning models hosted as service

	METHOD connect(params):
		# Implement the connect method for hosted ML service

	CLASS METHOD _create_instance(cls)
		# initialize a ML Backend instance

CLASS OtherBackend IMPLEMENTS Backend:
	...
	[you get the idea]

To create an instance of a backend, using the ML class as an example, use the get_instance() method:

>>> backend = ML.get_instance()

To centralize the creation of Backend instances based on specific Django settings(e.g. dev vs. production environments), create a base BackendFactory abstract class. This should follow the Factory Design Pattern.

# Abstract class for the Factory to instantiate the Backend based on Django Settings
ABSTRACT CLASS BackendFactory:
	ABSTRACT METHOD create_backend(self) -> Backend
		pass

The BackendFactory's create_backend method optionally allows a Backend instance to be injected into the factory instead of relying solely on Django settings. This is particularly useful if we want to explicitly specify the backend to use.

Creating Adapter that accepts any Backend

The Adapter class can be initialized with a Backend instance which provides a make_request method that forwards requests to the chosen Backend while adhering to its specific request and response formats.

CLASS Adapter:

  METHOD __init__(self, backend)
	# Initialize the Backend with BackendFactory
	SET backend = backend

  METHOD request(self):
	# something
	return self.backend.request()

  METHOD response(self):
	# something
	return self.backend.response()

With this Adapter class in place, we can create Adapter that are able interact with any backend we need.

CLASS Recommendation INHERITS ADAPTER:
	METHOD generateEmbeddings(self, params) -> Boolean
		# [ Implementation ]

	METHOD getRecommendation(self, params) -> Array
		# [ Implementation ]

CLASS Transcription INHERITS ADAPTER:
	METHOD generateCaption(self, params) -> Array
		# [ Implementation ]

CLASS OtherAdapter INHERITS ADAPTER:
	METHOD someOperation(self, params) -> Any
		# Operation that any backend wants

Below is a sample use case, using the ML backend as an example:

>>> backend = ML.get_instance()
>>> adapter =  Transcription(backend)

To access specific methods within the adapter:

>>> adapter.generateCaption(...)

Resources

OOP Design patterns

Acceptance Criteria

  1. A module with an appropriate name is created for the mini-library within the contentcuration app in Studio.
  2. Appropriate folders are created in the created module to clearly distinguish the files(if necessary).
  3. A base abstract Backend class that defines the operation that all backends must support is created.
  4. The Backend class includes relevant abstract methods that allow for specific implementations for the various backends.
  5. The Backend class implements the Singleton pattern.
  6. A base abstract Backend Factory class that centralizes the creation of Backend instances based on Django settings is created.
  7. All Backend instances specified in the BackendFactory follow the singleton pattern, ensuring only one instance of each backend can exist.
  8. A base Adapter class is established, which can be initialized by any Backend.
  9. The Adapter class includes methods that are relevant for interaction with the initialized Backend.
  10. Tests are written to validate the correctness of the base logic implementation.
  11. Documentation is updated to include information about the new mini-library, its usage, and its purpose.

Out of scope

  • This task doesn't include implementation of the the backends and adapters. They were only used to paint a picture of the architecture as a whole and how it works together.
### Tasks

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions