getAdapter(adapterName, adapterClassName, config) {
        if (!adapterName || !adapterClassName) {
            throw new errors.IncorrectUsageError({
                message: 'getAdapter must be called with a adapterName and a adapterClassName.'
            });
        }

        let adapterType;
        if (adapterName.includes(':')) {
            [adapterType] = adapterName.split(':');
        } else {
            adapterType = adapterName;
        }

        const adapterCache = this.instanceCache[adapterType];

        if (!adapterCache) {
            throw new errors.NotFoundError({
                message: `Unknown adapter type ${adapterType}. Please register adapter.`
            });
        }

        // @NOTE: example cache key value 'email:newsletters:custom-newsletter-adapter'
        const adapterCacheKey = `${adapterName}:${adapterClassName}`;
        if (adapterCache[adapterCacheKey]) {
            return adapterCache[adapterCacheKey];
        }

        /** @type AdapterConstructor */
        let Adapter;
        for (const pathToAdapters of this.pathsToAdapters) {
            let pathToAdapter = path.join(pathToAdapters, adapterType, adapterClassName);
            if (pathToAdapters === '') {
                // We are loading from node_modules, we can remove the `adapterType` prefix
                pathToAdapter = path.join(pathToAdapters, adapterClassName);
            }
            try {
                Adapter = this.loadAdapterFromPath(pathToAdapter);
                if (Adapter) {
                    break;
                }
            } catch (err) {
                // Catch runtime errors
                if (err.code !== 'MODULE_NOT_FOUND') {
                    throw new errors.IncorrectUsageError({err});
                }

                // Catch missing dependencies BUT NOT missing adapter
                if (!err.message.includes(pathToAdapter)) {
                    throw new errors.IncorrectUsageError({
                        message: `You are missing dependencies in your adapter ${pathToAdapter}`,
                        err
                    });
                }
            }
        }

        if (!Adapter) {
            throw new errors.IncorrectUsageError({
                message: `Unable to find ${adapterType} adapter ${adapterClassName} in ${this.pathsToAdapters}.`
            });
        }

        const adapter = new Adapter(config);

        if (!(adapter instanceof this.baseClasses[adapterType])) {
            if (Object.getPrototypeOf(Adapter).name !== this.baseClasses[adapterType].name) {
                throw new errors.IncorrectUsageError({
                    message: `${adapterType} adapter ${adapterClassName} does not inherit from the base class.`
                });
            }
        }

        if (!adapter.requiredFns) {
            throw new errors.IncorrectUsageError({
                message: `${adapterType} adapter ${adapterClassName} does not have the requiredFns.`
            });
        }

        for (const requiredFn of adapter.requiredFns) {
            if (typeof adapter[requiredFn] !== 'function') {
                throw new errors.IncorrectUsageError({
                    message: `${adapterType} adapter ${adapterClassName} is missing the ${requiredFn} method.`
                });
            }
        }

        adapterCache[adapterCacheKey] = adapter;

        return adapter;
    }
getAdapter

@param {string} adapterName The name of the type of adapter, e.g. "storage" or "scheduling", optionally including the feature, e.g. "storage:files"
@param {string} adapterClassName The active adapter instance class name e.g. "LocalFileStorage"
@param {object} [config] The config the adapter could be instantiated with

@returns {Adapter} The resolved and instantiated adapter