diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a87e40871e6860cb1f4a8e813ddbdb35912aadc2..2191218e5bbd3a8be2bea5ea148222a0120b6123 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -33,7 +33,7 @@ test:
   - pip install --cache-dir="$PIP_CACHE_DIR" unittest-xml-reporting coverage
   - pip install --cache-dir="$PIP_CACHE_DIR" -r requirements.txt
   # Setup test config and CouchDB database server
-  - echo -e "[couchdb]\nurl = http://couchdb:5984" > test/test_config.ini
+  - echo -e "[couchdb]\nurl = http://couchdb:5984\n" > test/test_config.ini
   - python test/_helper/setup_testdb.py -u "$COUCHDB_USER" -p "$COUCHDB_PASSWORD"
   # Add source directory to PYTHONPATH to allow testing our CLI scripts, which import our modules
   - export PYTHONPATH=".:$PYTHONPATH"
diff --git a/aas/backend/__init__.py b/aas/backend/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/aas/backends.py b/aas/backend/backends.py
similarity index 99%
rename from aas/backends.py
rename to aas/backend/backends.py
index 9cfad1239f2d307c07353ba152aee77cefe91b11..e5eaf4b75ab51099ee0f85d0253d54abbeb93746 100644
--- a/aas/backends.py
+++ b/aas/backend/backends.py
@@ -26,7 +26,7 @@ import re
 from typing import List, Dict, Type, TYPE_CHECKING
 
 if TYPE_CHECKING:
-    from .model import Referable
+    from ..model import Referable
 
 
 class Backend(metaclass=abc.ABCMeta):
diff --git a/aas/adapter/couchdb.py b/aas/backend/couchdb.py
similarity index 53%
rename from aas/adapter/couchdb.py
rename to aas/backend/couchdb.py
index 4ecd976e7866c7401bc938ef02440243d3671ef3..125dc0d5c81c09f0db01a8b800149758c70a22ff 100644
--- a/aas/adapter/couchdb.py
+++ b/aas/backend/couchdb.py
@@ -9,121 +9,238 @@
 # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 # specific language governing permissions and limitations under the License.
 """
-CouchDB backend for persistently storing AAS objects
+Todo: Add module docstring
+"""
+import threading
+import weakref
+from typing import List, Dict, Any, Optional, Iterator, Iterable, Union
+import re
+import urllib.parse
+import urllib.request
+import urllib.error
+import logging
+import json
+import http.client
 
-This module provides the `CouchDBObjectStore` class, that implements the AbstractObjectStore interface for storing and
-retrieving Identifiable PyI40AAS objects in/from a CouchDB server. The objects are serialized to JSON using the
-aas.adapter.json package and transferred to the configured CouchDB database.
+from . import backends
+from aas.adapter.json import json_deserialization, json_serialization
+from aas import model
 
-Typical usage:
 
-    database = CouchDBObjectStore('localhost:5984', 'aas_test')
-    database.login('user', 'password')
+logger = logging.getLogger(__name__)
 
-    submodel = aas.model.Submodel(...)
-    database.add(submodel)
 
-    aas = database.get_identifiable(aas.model.Identifier('https://acplt.org/MyAAS', aas.model.IdentifierType.IRI))
-    aas.description['de'] = "Eine neue Beschreibung"
-    aas.commit_changes()
+class CouchDBBackend(backends.Backend):
+    """
+    This Backend stores each Identifiable object as a single JSON document in the configured CouchDB database. Each
+    document's id is build from the object's identifier using the pattern {idtype}-{idvalue}; the document's contents
+    comprise a single property "data", containing the JSON serialization of the PyI40AAS object. The aas.adapter.json
+    package is used for serialization and deserialization of objects.
+    """
+    @classmethod
+    def update_object(cls,
+                      updated_object: "Referable",  # type: ignore
+                      store_object: "Referable",  # type: ignore
+                      relative_path: List[str]) -> None:
+
+        if not isinstance(store_object, model.Identifiable):
+            raise CouchDBSourceError("The given store_object is not Identifiable, therefore cannot be found "
+                                     "in the CouchDB")
+        url = CouchDBBackend._parse_source(store_object.source)
+        request = urllib.request.Request(url,
+                                         headers={'Accept': 'application/json'})
+        try:
+            data = CouchDBBackend.do_request(request)
+        except CouchDBServerError as e:
+            if e.code == 404:
+                raise KeyError("No Identifiable found in CouchDB at {}".format(url)) from e
+            raise
 
-    database.logout()
+        updated_store_object = data['data']
+        set_couchdb_revision(url, data["_rev"])
+        store_object.update_from(updated_store_object)
 
-To allow committing changes, the objects retrieved from the CouchDBObjectStore are instances of special classes
-(`CouchDBAssetAdministrationShell`, etc.), inheriting from the special base class `CouchDBIdentifiable`. However, these
-classes also inherit from the appropriate class in `aas.model` to be used as any other PyI40AAS object.
+    @classmethod
+    def commit_object(cls,
+                      committed_object: "Referable",  # type: ignore
+                      store_object: "Referable",  # type: ignore
+                      relative_path: List[str]) -> None:
+        if not isinstance(store_object, model.Identifiable):
+            raise CouchDBSourceError("The given store_object is not Identifiable, therefore cannot be found "
+                                     "in the CouchDB")
+        url = CouchDBBackend._parse_source(store_object.source)
+        # We need to get the revision of the object, if it already exists, otherwise we cannot write to the Couchdb
+        if get_couchdb_revision(url) is None:
+            raise CouchDBConflictError("No revision found for the given object. Try calling `update` on it.")
+
+        data = json.dumps({'data': store_object, "_rev": get_couchdb_revision(url)},
+                          cls=json_serialization.AASToJsonEncoder)
+        request = urllib.request.Request(
+            url,
+            headers={'Content-type': 'application/json'},
+            method='PUT',
+            data=data.encode())
+        try:
+            response = CouchDBBackend.do_request(request)
+            set_couchdb_revision(url, response["rev"])
+        except CouchDBServerError as e:
+            if e.code == 409:
+                raise CouchDBConflictError("Could not commit changes to id {} due to a concurrent modification in the "
+                                           "database.".format(store_object.identification)) from e
+            elif e.code == 404:
+                raise KeyError("Object with id {} was not found in the CouchDB at {}"
+                               .format(store_object.identification, url)) from e
+            raise
 
-Additionally, this module defines a custom Exception class `CouchDBError` and some subclasses. These Exceptions are used
-to unambiguously report errors (connection errors, parser errors or HTTP errors from the server) when interacting with
-the CouchDB server.
-"""
+    @classmethod
+    def _parse_source(cls, source: str) -> str:
+        """
+        Parses the source parameter of a model.Referable object
 
-import abc
-import http.client
-import http.cookiejar
-import json
-from typing import Iterator, Dict, Optional, Any, Iterable, Union
-import urllib.parse
-import urllib.request
-import urllib.error
-import threading
-import logging
+        :param source: Source string of the model.Referable object
+        :return: URL to the document
+        :raises CouchDBBackendSourceError, if the source has the wrong format
+        """
+        couchdb_s = re.match("couchdbs://", source)  # Note: Works, since match only checks the beginning of the string
+        if couchdb_s:
+            url = source.replace("couchdbs://", "https://", 1)
+        else:
+            couchdb_wo_s = re.match("couchdb://", source)
+            if couchdb_wo_s:
+                url = source.replace("couchdb://", "http://", 1)
+            else:
+                raise CouchDBSourceError("Source has wrong format. "
+                                         "Expected to start with {couchdb://, couchdbs://}, got {" + source + "}")
+        return url
 
-from .. import model
-from .json import StrictAASFromJsonDecoder, AASToJsonEncoder
+    @classmethod
+    def do_request(cls, request: urllib.request.Request) -> Dict[str, Any]:
+        """
+        Perform an HTTP request to the CouchDBServer, parse the result and handle errors
 
-logger = logging.getLogger(__name__)
+        :param request:
+        :return:
+        """
+        opener = urllib.request.build_opener(urllib.request.HTTPBasicAuthHandler(_credentials_store))
+        try:
+            response = opener.open(request)
+        except urllib.error.HTTPError as e:
+            logger.debug("Request %s %s finished with HTTP status code %s.",
+                         request.get_method(), request.full_url, e.code)
+            if e.headers.get('Content-type', None) != 'application/json':
+                raise CouchDBResponseError("Unexpected Content-type header {} of response from CouchDB server"
+                                           .format(e.headers.get('Content-type', None)))
 
+            if request.get_method() == 'HEAD':
+                raise CouchDBServerError(e.code, "", "", "HTTP {}") from e
 
-class CouchDBObjectStore(model.AbstractObjectStore):
+            try:
+                data = json.load(e)
+            except json.JSONDecodeError:
+                raise CouchDBResponseError("Could not parse error message of HTTP {}"
+                                           .format(e.code))
+            raise CouchDBServerError(e.code, data['error'], data['reason'],
+                                     "HTTP {}: {} (reason: {})".format(e.code, data['error'], data['reason'])) from e
+        except urllib.error.URLError as e:
+            raise CouchDBConnectionError("Error while connecting to the CouchDB server: {}".format(e)) from e
+
+        # Check response & parse data
+        assert (isinstance(response, http.client.HTTPResponse))
+        logger.debug("Request %s %s finished successfully.", request.get_method(), request.full_url)
+        if request.get_method() == 'HEAD':
+            return {}
+
+        if response.getheader('Content-type') != 'application/json':
+            raise CouchDBResponseError("Unexpected Content-type header")
+        try:
+            data = json.load(response, cls=json_deserialization.AASFromJsonDecoder)
+        except json.JSONDecodeError as e:
+            raise CouchDBResponseError("Could not parse CouchDB server response as JSON data.") from e
+        return data
+
+
+# Global registry for credentials for CouchDB Servers
+_credentials_store: urllib.request.HTTPPasswordMgrWithPriorAuth = urllib.request.HTTPPasswordMgrWithPriorAuth()
+# Note: The HTTPPasswordMgr is not thread safe during writing, should be thread safe for reading only.
+
+
+def register_credentials(url: str, username: str, password: str):
     """
-    An ObjectStore implementation for Identifiable PyI40AAS objects backed by a CouchDB database server.
+    Register the credentials of a CouchDB server to the global credentials store
 
-    This ObjectStore stores each Identifiable object as a single JSON document in the configured CouchDB database. Each
-    document's id is build from the object's identifier using the pattern {idtype}-{idvalue}; the document's contents
-    comprise a single property "data", containing the JSON serialization of the PyI40AAS object. The aas.adapter.json
-    package is used for serialization and deserialization of objects.
+    Warning: Do not use this function, while other threads may be accessing the credentials via the CouchDBObjectStore
+             or update or commit functions of model.base.Referable objects!
 
-    Objects retrieved from the CouchDBObjectStore are instances of the appropriate PyI40AAS model class. Additionally,
-    they inherit from the special base class `CouchDBIdentifiable`. It provides a `commit()` method to write back
-    changes, which have been made to the object, to the database.
+    :param url: Toplevel URL
+    :param username: Username to that CouchDB instance
+    :param password: Password to the Username
+    """
+    _credentials_store.add_password(None, url, username, password, is_authenticated=True)
 
-    All methods of the `CouchDBObjectStore` are blocking, i.e. they stop the current thread's execution until they
-    receive a response from the CouchDB server (or encounter a timeout). However, the `CouchDBObjectStore` objects are
-    thread-safe, meaning that you may run multiple method calls on the same CouchDBObjectStore in parallel in different
-    threads. For example, you could use a ThreadPoolExecutor to add a large number of objects to the database:
 
-        import concurrent.futures
-        submodels = [submodel1, submodel2, submodel3]
-        database = CouchDBObjectStore('localhost:5984', 'aas_test')
-        database.login('test', 'test')
-        with concurrent.futures.ThreadPoolExecutor() as pool:
-            pool.map(database.add, submodels)
+# Global registry for CouchDB Revisions
+_revision_store_lock = threading.Lock()
+_revision_store: Dict[str, str] = {}
+
 
+def set_couchdb_revision(url: str, revision: str):
     """
-    def __init__(self, url: str, database: str):
-        self.url = url
-        self.database_name = database
+    Set the CouchDB revision of the given document in the revision store
 
-        # Build shared cookie jar for session caching and thread-local store for OpenerDirector
-        self._cookie_jar = http.cookiejar.CookieJar()
-        self._thread_local = threading.local()
+    :param url: URL to the CouchDB document
+    :param revision: CouchDB revision
+    """
+    with _revision_store_lock:
+        _revision_store[url] = revision
 
-    # TODO method to delete database
 
-    def login(self, user: str, password: str):
-        """
-        Login at the CouchDB server with the given user credentials.
+def get_couchdb_revision(url: str) -> Optional[str]:
+    """
+    Get the CouchDB revision from the revision store for the given URL to a CouchDB Document
 
-        This method uses the /_session endpoint of the CouchDB server to obtain a session cookie, which is used for
-        further HTTP requests. This is required to be performed before any other request to the object store, unless
-        the CouchDB server does not require authentication.
+    :param url: URL to the CouchDB document
+    :return: CouchDB-revision, if there is one, otherwise returns None
+    """
+    with _revision_store_lock:
+        return _revision_store.get(url)
 
-        :raises CouchDBError: If error occur during the request to the CouchDB server (see `_do_request()` for details)
-        """
-        logger.info("Logging in to CouchDB server %s with user %s ...", self.url, user)
-        request = urllib.request.Request(
-            "{}/_session".format(self.url),
-            headers={'Content-type': 'application/json'},
-            method='POST',
-            data=json.dumps({'name': user, 'password': password}).encode())
-        self._do_request(request)
 
-    def logout(self):
-        """
-        Logout from the CouchDB server.
+def delete_couchdb_revision(url: str):
+    """
+    Delete the CouchDB revision from the revision store for the given URL to a CouchDB Document
 
-        This method uses the /_session endpoint of the CouchDB server to invalidate the user session and delete the
-        session cookie.
+    :param url: URL to the CouchDB document
+    """
+    with _revision_store_lock:
+        del _revision_store[url]
 
-        :raises CouchDBError: If error occur during the request to the CouchDB server (see `_do_request()` for details)
+
+class CouchDBObjectStore(model.AbstractObjectStore):
+    """
+    An ObjectStore implementation for Identifiable PyI40AAS objects backed by a CouchDB database server.
+
+    All methods of the `CouchDBObjectStore` are blocking, i.e. they stop the current thread's execution until they
+    receive a response from the CouchDB server (or encounter a timeout). However, the `CouchDBObjectStore` objects are
+    thread-safe, as long as no CouchDB credentials are added (via `register_credentials()`) during transactions.
+    """
+    def __init__(self, url: str, database: str):
         """
-        logger.info("Logging out from CouchDB server %s ...", self.url)
-        request = urllib.request.Request(
-            "{}/_session".format(self.url),
-            headers={'Content-type': 'application/json'},
-            method='DELETE')
-        self._do_request(request)
+        Initializer of class CouchDBObjectStore
+
+        :param url: URL to the CouchDB
+        :param database: Name of the Database inside the CouchDB
+        """
+        self.url: str = url
+        self.database_name: str = database
+
+        # A dictionary of weak references to local replications of stored objects. Objects are kept in this cache as
+        # long as there is any other reference in the Python application to them. We use this to make sure that only one
+        # local replication of each object is kept in the application and retrieving an object from the store always
+        # returns the **same** (not only equal) object. Still, objects are forgotten, when they are not referenced
+        # anywhere else to save memory.
+        self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable]\
+            = weakref.WeakValueDictionary()
+        self._object_cache_lock = threading.Lock()
 
     def check_database(self, create=False):
         """
@@ -137,7 +254,7 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             headers={'Accept': 'application/json'},
             method='HEAD')
         try:
-            self._do_request(request)
+            CouchDBBackend.do_request(request)
         except CouchDBServerError as e:
             # If an HTTPError is raised, re-raise it, unless it is a 404 error and we are requested to create the
             # database
@@ -153,12 +270,15 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             "{}/{}".format(self.url, self.database_name),
             headers={'Accept': 'application/json'},
             method='PUT')
-        self._do_request(request)
+        CouchDBBackend.do_request(request)
 
-    def get_identifiable(self, identifier: Union[str, model.Identifier]) -> "CouchDBIdentifiable":
+    def get_identifiable(self, identifier: Union[str, model.Identifier]) -> model.Identifiable:
         """
         Retrieve an AAS object from the CouchDB by its Identifier
 
+        If the identifier is a string, it is assumed that the string is a correct couchdb-ID-string (according to the
+        internal conversion rules, see CouchDBObjectStore._transform_id() )
+
         :raises KeyError: If no such object is stored in the database
         :raises CouchDBError: If error occur during the request to the CouchDB server (see `_do_request()` for details)
         """
@@ -170,7 +290,7 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             "{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(identifier, safe='')),
             headers={'Accept': 'application/json'})
         try:
-            data = self._do_request(request)
+            data = CouchDBBackend.do_request(request)
         except CouchDBServerError as e:
             if e.code == 404:
                 raise KeyError("No Identifiable with id {} found in CouchDB database".format(identifier)) from e
@@ -178,11 +298,25 @@ class CouchDBObjectStore(model.AbstractObjectStore):
 
         # Add CouchDB meta data (for later commits) to object
         obj = data['data']
-        if not isinstance(obj, CouchDBIdentifiable):
+        if not isinstance(obj, model.Identifiable):
             raise CouchDBResponseError("The CouchDB document with id {} does not contain an identifiable AAS object."
                                        .format(identifier))
-        obj._store = self
-        obj.couchdb_revision = data['_rev']
+        self.generate_source(obj)  # Generate the source parameter of this object
+        set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(identifier, safe='')),
+                             data["_rev"])
+
+        # If we still have a local replication of that object (since it is referenced from anywhere else), update that
+        # replication and return it.
+        with self._object_cache_lock:
+            if obj.identification in self._object_cache:
+                old_obj = self._object_cache[obj.identification]
+                # If the source does not match the correct source for this CouchDB backend, the object seems to belong
+                # to another backend now, so we return a fresh copy
+                if old_obj.source == obj.source:
+                    old_obj.update_from(obj)
+                    return old_obj
+
+        self._object_cache[obj.identification] = obj
         return obj
 
     def add(self, x: model.Identifiable) -> None:
@@ -194,7 +328,7 @@ class CouchDBObjectStore(model.AbstractObjectStore):
         """
         logger.debug("Adding object %s to CouchDB database ...", repr(x))
         # Serialize data
-        data = json.dumps({'data': x}, cls=AASToJsonEncoder)
+        data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder)
 
         # Create and issue HTTP request (raises HTTPError on status != 200)
         request = urllib.request.Request(
@@ -203,44 +337,17 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             method='PUT',
             data=data.encode())
         try:
-            self._do_request(request)
+            response = CouchDBBackend.do_request(request)
+            set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.identification)),
+                                 response["rev"])
         except CouchDBServerError as e:
             if e.code == 409:
                 raise KeyError("Identifiable with id {} already exists in CouchDB database".format(x.identification))\
                     from e
             raise
-
-    def commit(self, x: "CouchDBIdentifiable") -> None:
-        """
-        Commit in-memory changes in a CouchDBIdentifiable PyI40AAS object to the database
-
-        :param x: The changed object
-        :raises KeyError: If the object does not exist in the database
-        :raises CouchDBConflictError: If a concurrent modification (or deletion) in the database was detected
-        :raises CouchDBError: If error occur during the request to the CouchDB server (see `_do_request()` for details)
-        """
-        logger.debug("Committing changes of object %s based on revision %s to CouchDB database ...",
-                     repr(x), x.couchdb_revision)
-        # Serialize data
-        data = json.dumps({'data': x, '_rev': x.couchdb_revision}, cls=AASToJsonEncoder)
-
-        # Create and issue HTTP request (raises HTTPError on status != 200)
-        request = urllib.request.Request(
-            "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.identification)),
-            headers={'Content-type': 'application/json'},
-            method='PUT',
-            data=data.encode())
-        try:
-            response_data = self._do_request(request)
-        except CouchDBServerError as e:
-            if e.code == 409:
-                raise CouchDBConflictError("Could not commit changes to id {} due to a concurrent modification in the "
-                                           "database.".format(x.identification)) from e
-            elif e.code == 404:
-                raise KeyError("Object with id {} was not found in the database {}"
-                               .format(x.identification, self.database_name)) from e
-            raise
-        x.couchdb_revision = response_data['rev']
+        with self._object_cache_lock:
+            self._object_cache[x.identification] = x
+        self.generate_source(x)  # Set the source of the object
 
     def discard(self, x: model.Identifiable, safe_delete=False) -> None:
         """
@@ -255,34 +362,52 @@ class CouchDBObjectStore(model.AbstractObjectStore):
         :raises CouchDBError: If error occur during the request to the CouchDB server (see `_do_request()` for details)
         """
         logger.debug("Deleting object %s from CouchDB database ...", repr(x))
-        # If x is not a CouchDBIdentifiable, retrieve x from the database to get the current couchdb_revision
-        if hasattr(x, 'couchdb_revision') and safe_delete:
-            rev = x.couchdb_revision  # type: ignore
-            logger.debug("using the object's stored revision token %s for deletion.",
-                         x.couchdb_revision)  # type: ignore
+        rev = get_couchdb_revision("{}/{}/{}".format(self.url,
+                                                     self.database_name,
+                                                     self._transform_id(x.identification)))
+
+        if rev is not None and safe_delete:
+            logger.debug("using the object's stored revision token %s for deletion." % rev)
+        elif safe_delete:
+            raise CouchDBConflictError("No CouchDBRevision found for the object")
         else:
+            # If not safe_delete, fetch the current document revision from the database using a HEAD request and the
+            # ETag response header
             try:
                 logger.debug("fetching the current object revision for deletion ...")
-                current = self.get_identifiable(x.identification)
-            except KeyError as e:
-                raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.identification)) from e
-            rev = current.couchdb_revision
-            logger.debug("using the current object revision %s for deletion.")
+                request = urllib.request.Request(
+                    "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.identification)),
+                    headers={'Accept': 'application/json'},
+                    method='HEAD')
+                opener = urllib.request.build_opener(urllib.request.HTTPBasicAuthHandler(_credentials_store))
+                response = opener.open(request)
+                rev = response.getheader('ETag')[1:-1]
+            except urllib.error.HTTPError as e:
+                if e.code == 404:
+                    raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.identification))\
+                        from e
+                raise
 
         request = urllib.request.Request(
             "{}/{}/{}?rev={}".format(self.url, self.database_name, self._transform_id(x.identification), rev),
             headers={'Content-type': 'application/json'},
             method='DELETE')
         try:
-            self._do_request(request)
+            CouchDBBackend.do_request(request)
         except CouchDBServerError as e:
             if e.code == 404:
                 raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.identification)) from e
             elif e.code == 409:
                 raise CouchDBConflictError(
-                    "Object with id {} has been modified in the database since the version requested to be deleted."
-                    .format(x.identification)) from e
+                    "Object with id {} has been modified in the database since "
+                    "the version requested to be deleted.".format(x.identification)) from e
             raise
+        delete_couchdb_revision("{}/{}/{}".format(self.url,
+                                                  self.database_name,
+                                                  self._transform_id(x.identification)))
+        with self._object_cache_lock:
+            del self._object_cache[x.identification]
+        x.source = ""
 
     def __contains__(self, x: object) -> bool:
         """
@@ -299,13 +424,13 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             identifier = x.identification
         else:
             return False
-        logger.debug("Checking existance of object with id %s in database ...", repr(x))
+        logger.debug("Checking existence of object with id %s in database ...", repr(x))
         request = urllib.request.Request(
             "{}/{}/{}".format(self.url, self.database_name, self._transform_id(identifier)),
             headers={'Accept': 'application/json'},
             method='HEAD')
         try:
-            self._do_request(request)
+            CouchDBBackend.do_request(request)
         except CouchDBServerError as e:
             if e.code == 404:
                 return False
@@ -323,7 +448,7 @@ class CouchDBObjectStore(model.AbstractObjectStore):
         request = urllib.request.Request(
             "{}/{}".format(self.url, self.database_name),
             headers={'Accept': 'application/json'})
-        data = self._do_request(request)
+        data = CouchDBBackend.do_request(request)
         return data['doc_count']
 
     def __iter__(self) -> Iterator[model.Identifiable]:
@@ -351,66 +476,9 @@ class CouchDBObjectStore(model.AbstractObjectStore):
         request = urllib.request.Request(
             "{}/{}/_all_docs".format(self.url, self.database_name),
             headers={'Accept': 'application/json'})
-        data = self._do_request(request)
+        data = CouchDBBackend.do_request(request)
         return CouchDBIdentifiableIterator(self, (row['id'] for row in data['rows']))
 
-    def _do_request(self, request: urllib.request.Request) -> Dict[str, Any]:
-        """
-        Perform an HTTP request to the CouchDB server, parse the result and handle errors
-
-        This function performs the request described by the given Request object, checks the response status code and
-        either raises a CouchDBError or returns the parsed JSON response data.
-
-        :raises CouchDBServerError: When receiving an HTTP status code != 200
-        :raises CouchDBResponseError: When the HTTP response could not be parsed
-        :raises CouchDBConnectionError: On errors while connecting to the CouchDB server
-        """
-        # Create thread-local OpenerDirector with shared cookie jar if not existing in this thread
-        if hasattr(self._thread_local, 'opener'):
-            opener = self._thread_local.opener
-        else:
-            logger.debug("Creating new urllib OpenerDirector for current thread.")
-            opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(self._cookie_jar))
-            self._thread_local.opener = opener
-
-        # Do request and handle HTTP Errors
-        logger.debug("Sending HTTP request to CouchDB server: %s %s ...", request.get_method(), request.full_url)
-        try:
-            response = opener.open(request)
-        except urllib.error.HTTPError as e:
-            logger.debug("Request %s %s finished with HTTP status code %s.",
-                         request.get_method(), request.full_url, e.code)
-            if e.headers.get('Content-type', None) != 'application/json':
-                raise CouchDBResponseError("Unexpected Content-type header {} of response from CouchDB server"
-                                           .format(e.headers.get('Content-type', None)))
-
-            if request.get_method() == 'HEAD':
-                raise CouchDBServerError(e.code, "", "", "HTTP {}") from e
-
-            try:
-                data = json.load(e)
-            except json.JSONDecodeError:
-                raise CouchDBResponseError("Could not parse error message of HTTP {}"
-                                           .format(e.code))
-            raise CouchDBServerError(e.code, data['error'], data['reason'],
-                                     "HTTP {}: {} (reason: {})".format(e.code, data['error'], data['reason'])) from e
-        except urllib.error.URLError as e:
-            raise CouchDBConnectionError("Error while connecting to the CouchDB server: {}".format(e)) from e
-
-        # Check response & parse data
-        assert (isinstance(response, http.client.HTTPResponse))
-        logger.debug("Request %s %s finished successfully.", request.get_method(), request.full_url)
-        if request.get_method() == 'HEAD':
-            return {}
-
-        if response.getheader('Content-type') != 'application/json':
-            raise CouchDBResponseError("Unexpected Content-type header")
-        try:
-            data = json.load(response, cls=CouchDBJSONDecoder)
-        except json.JSONDecodeError as e:
-            raise CouchDBResponseError("Could not parse CouchDB server response as JSON data.") from e
-        return data
-
     @staticmethod
     def _transform_id(identifier: model.Identifier, url_quote=True) -> str:
         """
@@ -423,99 +491,42 @@ class CouchDBObjectStore(model.AbstractObjectStore):
             result = urllib.parse.quote(result, safe='')
         return result
 
+    def generate_source(self, identifiable: model.Identifiable):
+        """
+        Generates the source string for an Identifiable object that is backed by the Couchdb
 
-# #################################################################################################
-# Special object classes for Identifiable PyI40AAS objects retrieved from the CouchDBObjectStore
-
-class CouchDBIdentifiable(model.Identifiable, metaclass=abc.ABCMeta):
-    """
-    Special base class for Identifiable PyI40AAS retrieved from the CouchDBObjectStore, allowing to write back (commit)
-    changes to the database.
-
-    This is an abstract base class. For each Identifiable AAS object type, there is one subclass, inheriting from this
-    abstract base class and the appropriate aas.model class.
-
-    This base class provides the `commit_changes()` method and the `_store` and `couchdb_revision` attributes required
-    to perform the commit action. `_store` holds a reference to the CouchDBObjectStore instance; `couchdb_revision`
-    contains the CouchDB document revision token of the latest object revision in the database. It is transferred to
-    CouchDB when committing changes to check for editing conflicts.
-    """
-
-    def __init__(self) -> None:
-        super().__init__()
-        self._store: Optional[CouchDBObjectStore] = None
-        self.couchdb_revision: Optional[str] = None
-
-    def commit_changes(self) -> None:
-        if self._store is None:
-            raise ValueError("CouchDBIdentifiable is not associated with a store")
-        self._store.commit(self)
-
-
-class CouchDBAssetAdministrationShell(model.AssetAdministrationShell, CouchDBIdentifiable):
-    pass
-
-
-class CouchDBAsset(model.Asset, CouchDBIdentifiable):
-    pass
-
-
-class CouchDBConceptDescription(model.ConceptDescription, CouchDBIdentifiable):
-    pass
-
-
-class CouchDBSubmodel(model.Submodel, CouchDBIdentifiable):
-    pass
-
-
-class CouchDBJSONDecoder(StrictAASFromJsonDecoder):
-    """
-    Special json.JSONDecoder class for deserializing AAS objects received from the CouchDB server
-
-    This class inherits from StrictAASFromJsonDecoder to deserialize AAS JSON structures into the corresponding PyI40AAS
-    object classes. However, it overrides the constructor methods of all Identifiable AAS objects to create instances of
-    the `CouchDBIdentifiable` classes, defined above, instead of the usual aas.model classes.
-    """
-    @classmethod
-    def _construct_asset_administration_shell(
-            cls, dct: Dict[str, object], object_class=model.AssetAdministrationShell) -> model.AssetAdministrationShell:
-        return super()._construct_asset_administration_shell(dct, object_class=CouchDBAssetAdministrationShell)
-
-    @classmethod
-    def _construct_asset(cls, dct: Dict[str, object], object_class=model.Asset) -> model.Asset:
-        return super()._construct_asset(dct, object_class=CouchDBAsset)
-
-    @classmethod
-    def _construct_concept_description(cls, dct: Dict[str, object], object_class=model.ConceptDescription)\
-            -> model.ConceptDescription:
-        return super()._construct_concept_description(dct, object_class=CouchDBConceptDescription)
-
-    @classmethod
-    def _construct_submodel(cls, dct: Dict[str, object], object_class=model.Submodel) -> model.Submodel:
-        return super()._construct_submodel(dct, object_class=CouchDBSubmodel)
+        :param identifiable: Identifiable object
+        """
+        source: str = self.url.replace("https://", "couchdbs://").replace("http://", "couchdb://")
+        source += "/" + self.database_name + "/" + self._transform_id(identifiable.identification)
+        identifiable.source = source
 
 
 # #################################################################################################
 # Custom Exception classes for reporting errors during interaction with the CouchDB server
 
 class CouchDBError(Exception):
-    """Base class of all exceptions raised by the CouchDBObjectStore"""
+    pass
+
+
+class CouchDBSourceError(CouchDBError):
+    """Exception raised when the source has the wrong format"""
     pass
 
 
 class CouchDBConnectionError(CouchDBError):
-    """Exception raised by the CouchDBObjectStore when the CouchDB server could not be reached"""
+    """Exception raised when the CouchDB server could not be reached"""
     pass
 
 
 class CouchDBResponseError(CouchDBError):
-    """Exception raised by the CouchDBObjectStore when an HTTP of the CouchDB server could not be handled (e.g.
+    """Exception raised by when an HTTP of the CouchDB server could not be handled (e.g.
     no JSON body)"""
     pass
 
 
 class CouchDBServerError(CouchDBError):
-    """Exception raised by the CouchDBObjectStore when the CouchDB server returns an unexpected error code"""
+    """Exception raised when the CouchDB server returns an unexpected error code"""
     def __init__(self, code: int, error: str, reason: str, *args):
         super().__init__(*args)
         self.code = code
@@ -524,6 +535,6 @@ class CouchDBServerError(CouchDBError):
 
 
 class CouchDBConflictError(CouchDBError):
-    """Exception raised by the CouchDBObjectStore when an object could not be committed due to an concurrent
+    """Exception raised when an object could not be committed due to an concurrent
     modification in the database"""
     pass
diff --git a/aas/model/base.py b/aas/model/base.py
index 045f4c175548d865d43e2a01ab2cfae93b637a7d..647c728bb16fa8a54ac0c0e2a607e768b682d124 100644
--- a/aas/model/base.py
+++ b/aas/model/base.py
@@ -22,7 +22,7 @@ from typing import List, Optional, Set, TypeVar, MutableSet, Generic, Iterable,
 import re
 
 from . import datatypes
-from .. import backends
+from ..backend import backends
 
 if TYPE_CHECKING:
     from . import provider
@@ -541,7 +541,7 @@ class Referable(metaclass=abc.ABCMeta):
             break
         return None, None
 
-    def update_from(self, other: "Referable"):
+    def update_from(self, other: "Referable", update_source: bool = False):
         """
         Internal function to updates the object's attributes from another object of a similar type.
 
@@ -549,9 +549,12 @@ class Referable(metaclass=abc.ABCMeta):
         protocol clients, etc.) to update the object's data, after `update()` has been called.
 
         :param other: The object to update from
+        :param update_source: Update the source attribute with the other's source attribute. This is not propagated
+                              recursively
         """
         for name, var in vars(other).items():
-            if name == "parent":  # do not update the parent
+            # do not update the parent or source (depending on update_source parameter)
+            if name == "parent" or name == "source" and not update_source:
                 continue
             if isinstance(var, NamespaceSet):
                 # update the elements of the NameSpaceSet
@@ -1126,7 +1129,7 @@ class NamespaceSet(MutableSet[_RT], Generic[_RT]):
                 referable = self._backend[other_referable.id_short]
                 if type(referable) is type(other_referable):
                     # referable is the same as other referable
-                    referable.update_from(other_referable)
+                    referable.update_from(other_referable, update_source=True)
             except KeyError:
                 # other referable is not in NamespaceSet
                 referables_to_add.append(other_referable)
diff --git a/test/adapter/test_couchdb.py b/test/adapter/test_couchdb.py
deleted file mode 100644
index 6b0a2a2ae8d47533e17bfda10d5e8d58c2fb11ed..0000000000000000000000000000000000000000
--- a/test/adapter/test_couchdb.py
+++ /dev/null
@@ -1,216 +0,0 @@
-# Copyright 2020 PyI40AAS Contributors
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-import base64
-import concurrent.futures
-import configparser
-import copy
-import os
-import unittest
-import urllib.request
-import urllib.error
-
-from aas.adapter import couchdb
-from aas.examples.data.example_aas import *
-
-
-TEST_CONFIG = configparser.ConfigParser()
-TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"),
-                  os.path.join(os.path.dirname(__file__), "..", "test_config.ini")))
-
-
-# Check if CouchDB database is avalable. Otherwise, skip tests.
-try:
-    request = urllib.request.Request(
-        "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']),
-        headers={
-            'Authorization': 'Basic %s' % base64.b64encode(
-                ('%s:%s' % (TEST_CONFIG['couchdb']['user'], TEST_CONFIG['couchdb']['password']))
-                .encode('ascii')).decode("ascii")
-            },
-        method='HEAD')
-    urllib.request.urlopen(request)
-    COUCHDB_OKAY = True
-    COUCHDB_ERROR = None
-except urllib.error.URLError as e:
-    COUCHDB_OKAY = False
-    COUCHDB_ERROR = e
-
-
-@unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'],
-                                                                                 TEST_CONFIG['couchdb']['database'],
-                                                                                 COUCHDB_ERROR))
-class CouchDBTest(unittest.TestCase):
-    def setUp(self) -> None:
-        # Create CouchDB store, login and check database
-        self.db = couchdb.CouchDBObjectStore(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database'])
-        self.db.login(TEST_CONFIG['couchdb']['user'], TEST_CONFIG['couchdb']['password'])
-        self.db.check_database()
-
-    def tearDown(self) -> None:
-        self.db.clear()
-        self.db.logout()
-
-    def test_example_submodel_storing(self) -> None:
-        example_submodel = create_example_submodel()
-
-        # Add exmaple submodel
-        self.db.add(example_submodel)
-        self.assertEqual(1, len(self.db))
-        self.assertIn(example_submodel, self.db)
-
-        # Restore example submodel and check data
-        submodel_restored = self.db.get_identifiable(
-            model.Identifier(id_='https://acplt.org/Test_Submodel', id_type=model.IdentifierType.IRI))
-        assert(isinstance(submodel_restored, model.Submodel))
-        checker = AASDataChecker(raise_immediately=True)
-        check_example_submodel(checker, submodel_restored)
-
-        # Delete example submodel
-        self.db.discard(submodel_restored)
-        self.assertNotIn(example_submodel, self.db)
-
-    def test_iterating(self) -> None:
-        example_data = create_full_example()
-
-        # Add all objects
-        for item in example_data:
-            self.db.add(item)
-
-        self.assertEqual(6, len(self.db))
-
-        # Iterate objects, add them to a DictObjectStore and check them
-        retrieved_data_store: model.provider.DictObjectStore[model.Identifiable] = model.provider.DictObjectStore()
-        for item in self.db:
-            retrieved_data_store.add(item)
-        checker = AASDataChecker(raise_immediately=True)
-        check_full_example(checker, retrieved_data_store)
-
-    def test_parallel_iterating(self) -> None:
-        example_data = create_full_example()
-        ids = [item.identification for item in example_data]
-
-        # Add objects via thread pool executor
-        with concurrent.futures.ThreadPoolExecutor() as pool:
-            result = pool.map(self.db.add, example_data)
-        list(result)  # Iterate Executor result to raise exceptions
-
-        self.assertEqual(6, len(self.db))
-
-        # Retrieve objects via thread pool executor
-        with concurrent.futures.ThreadPoolExecutor() as pool:
-            retrieved_objects = pool.map(self.db.get_identifiable, ids)
-
-        retrieved_data_store: model.provider.DictObjectStore[model.Identifiable] = model.provider.DictObjectStore()
-        for item in retrieved_objects:
-            retrieved_data_store.add(item)
-        self.assertEqual(6, len(retrieved_data_store))
-        checker = AASDataChecker(raise_immediately=True)
-        check_full_example(checker, retrieved_data_store)
-
-        # Delete objects via thread pool executor
-        with concurrent.futures.ThreadPoolExecutor() as pool:
-            result = pool.map(self.db.discard, example_data)
-        list(result)  # Iterate Executor result to raise exceptions
-
-        self.assertEqual(0, len(self.db))
-
-    def test_key_errors(self) -> None:
-        # Double adding an object should raise a KeyError
-        example_submodel = create_example_submodel()
-        self.db.add(example_submodel)
-        with self.assertRaises(KeyError) as cm:
-            self.db.add(example_submodel)
-        self.assertEqual("'Identifiable with id Identifier(IRI=https://acplt.org/Test_Submodel) already exists in "
-                         "CouchDB database'", str(cm.exception))
-
-        # Querying a deleted object should raise a KeyError
-        retrieved_submodel = self.db.get_identifiable(
-            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
-        self.db.discard(example_submodel)
-        with self.assertRaises(KeyError) as cm:
-            self.db.get_identifiable(model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
-        self.assertEqual("'No Identifiable with id IRI-https://acplt.org/Test_Submodel found in CouchDB database'",
-                         str(cm.exception))
-
-        # Double deleting should also raise a KeyError
-        with self.assertRaises(KeyError) as cm:
-            self.db.discard(retrieved_submodel)
-        self.assertEqual("'No AAS object with id Identifier(IRI=https://acplt.org/Test_Submodel) exists in "
-                         "CouchDB database'", str(cm.exception))
-
-    def test_conflict_errors(self) -> None:
-        # Preperation: add object and retrieve it from the database
-        example_submodel = create_example_submodel()
-        self.db.add(example_submodel)
-        retrieved_submodel = self.db.get_identifiable(
-            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
-
-        # Simulate a concurrent modification
-        remote_modified_submodel = copy.copy(retrieved_submodel)
-        remote_modified_submodel.id_short = "newIdShort"
-        remote_modified_submodel.commit_changes()
-
-        # Committing changes to the retrieved object should now raise a conflict error
-        retrieved_submodel.id_short = "myOtherNewIdShort"
-        with self.assertRaises(couchdb.CouchDBConflictError) as cm:
-            retrieved_submodel.commit_changes()
-        self.assertEqual("Could not commit changes to id Identifier(IRI=https://acplt.org/Test_Submodel) due to a "
-                         "concurrent modification in the database.", str(cm.exception))
-
-        # Deleting the submodel with safe_delete should also raise a conflict error. Deletion without safe_delete should
-        # work
-        with self.assertRaises(couchdb.CouchDBConflictError) as cm:
-            self.db.discard(retrieved_submodel, True)
-        self.assertEqual("Object with id Identifier(IRI=https://acplt.org/Test_Submodel) has been modified in the "
-                         "database since the version requested to be deleted.", str(cm.exception))
-        self.db.discard(retrieved_submodel, False)
-        self.assertEqual(0, len(self.db))
-
-        # Committing after deletion should also raise a conflict error
-        with self.assertRaises(couchdb.CouchDBConflictError) as cm:
-            retrieved_submodel.commit_changes()
-        self.assertEqual("Could not commit changes to id Identifier(IRI=https://acplt.org/Test_Submodel) due to a "
-                         "concurrent modification in the database.", str(cm.exception))
-
-    def test_editing(self) -> None:
-        example_submodel = create_example_submodel()
-        self.db.add(example_submodel)
-
-        # Retrieve submodel from database and change ExampleCapability's semanticId
-        submodel = self.db.get_identifiable(
-            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
-        assert(isinstance(submodel, couchdb.CouchDBSubmodel))
-        capability = submodel.submodel_element.get_referable('ExampleCapability')
-        capability.semantic_id = model.Reference((model.Key(type_=model.KeyElements.GLOBAL_REFERENCE,
-                                                  local=False,
-                                                  value='http://acplt.org/Capabilities/AnotherCapability',
-                                                  id_type=model.KeyType.IRDI),))
-
-        # Commit changes
-        submodel.commit_changes()
-
-        # Change ExampleSubmodelCollectionOrdered's description
-        collection = submodel.submodel_element.get_referable('ExampleSubmodelCollectionOrdered')
-        collection.description['de'] = "Eine sehr wichtige Sammlung von Elementen"   # type: ignore
-
-        # Commit changes
-        submodel.commit_changes()
-
-        # Check version in database
-        new_submodel = self.db.get_identifiable(
-            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
-        assert(isinstance(new_submodel, couchdb.CouchDBSubmodel))
-        capability = new_submodel.submodel_element.get_referable('ExampleCapability')
-        assert(isinstance(capability, model.Capability))
-        self.assertEqual('http://acplt.org/Capabilities/AnotherCapability',
-                         capability.semantic_id.key[0].value)  # type: ignore
-        collection = new_submodel.submodel_element.get_referable('ExampleSubmodelCollectionOrdered')
-        self.assertEqual("Eine sehr wichtige Sammlung von Elementen", collection.description['de'])  # type: ignore
diff --git a/test/backend/__init__.py b/test/backend/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/test_backends.py b/test/backend/test_backends.py
similarity index 86%
rename from test/test_backends.py
rename to test/backend/test_backends.py
index 89a3e0ff544fcc109686f7fbba9c0c91898dfde0..78b801c9fd369722ae425ad877e9bbcd2e7681c4 100644
--- a/test/test_backends.py
+++ b/test/backend/test_backends.py
@@ -1,12 +1,12 @@
 from unittest import mock
 import unittest
 
-from aas import backends
+from aas.backend import backends
 
 
 class BackendsTest(unittest.TestCase):
     def test_backend_store(self):
-        with mock.patch("aas.backends.Backend") as mock_backend:
+        with mock.patch("aas.backend.backends.Backend") as mock_backend:
             backends.register_backend("mockScheme", mock_backend)
             self.assertEqual(backends.get_backend("mockScheme:x-test:test_backend"), mock_backend)
 
diff --git a/test/backend/test_couchdb.py b/test/backend/test_couchdb.py
new file mode 100644
index 0000000000000000000000000000000000000000..13fb208f72e196d80a0e4ef3950987c6c8af1e5f
--- /dev/null
+++ b/test/backend/test_couchdb.py
@@ -0,0 +1,220 @@
+# Copyright 2020 PyI40AAS Contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+import base64
+import configparser
+import copy
+import os
+import unittest
+import unittest.mock
+import urllib.request
+import urllib.error
+
+from aas.backend import backends, couchdb
+from aas.examples.data.example_aas import *
+
+
+TEST_CONFIG = configparser.ConfigParser()
+TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"),
+                  os.path.join(os.path.dirname(__file__), "..", "test_config.ini")))
+
+source_core: str = "couchdb://" + TEST_CONFIG["couchdb"]["url"].lstrip("http://") + "/" + \
+                   TEST_CONFIG["couchdb"]["database"] + "/"
+
+
+# Check if CouchDB database is available. Otherwise, skip tests.
+try:
+    request = urllib.request.Request(
+        "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']),
+        headers={
+            'Authorization': 'Basic %s' % base64.b64encode(
+                ('%s:%s' % (TEST_CONFIG['couchdb']['user'], TEST_CONFIG['couchdb']['password']))
+                .encode('ascii')).decode("ascii")
+            },
+        method='HEAD')
+    urllib.request.urlopen(request)
+    COUCHDB_OKAY = True
+    COUCHDB_ERROR = None
+except urllib.error.URLError as e:
+    COUCHDB_OKAY = False
+    COUCHDB_ERROR = e
+
+
+class CouchDBBackendOfflineMethodsTest(unittest.TestCase):
+    def test_parse_source(self):
+        couchdb.register_credentials(url="couchdb.plt.rwth-aachen.de:5984",
+                                     username="test_user",
+                                     password="test_password")
+
+        url = couchdb.CouchDBBackend._parse_source(
+            "couchdbs://couchdb.plt.rwth-aachen.de:5984/path_to_db/path_to_doc"
+        )
+        expected_url = "https://couchdb.plt.rwth-aachen.de:5984/path_to_db/path_to_doc"
+        self.assertEqual(expected_url, url)
+
+        url = couchdb.CouchDBBackend._parse_source(
+            "couchdb://couchdb.plt.rwth-aachen.de:5984/path_to_db/path_to_doc"
+        )
+        expected_url = "http://couchdb.plt.rwth-aachen.de:5984/path_to_db/path_to_doc"
+        self.assertEqual(expected_url, url)
+
+        with self.assertRaises(couchdb.CouchDBSourceError) as cm:
+            couchdb.CouchDBBackend._parse_source("wrong_scheme:plt.rwth-aachen.couchdb:5984/path_to_db/path_to_doc")
+            self.assertEqual("Source has wrong format. "
+                             "Expected to start with {couchdb, couchdbs}, got "
+                             "{wrong_scheme:plt.rwth-aachen.couchdb:5984/path_to_db/path_to_doc}",
+                             cm.exception)
+
+
+@unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'],
+                                                                                 TEST_CONFIG['couchdb']['database'],
+                                                                                 COUCHDB_ERROR))
+class CouchDBBackendTest(unittest.TestCase):
+    def setUp(self) -> None:
+        self.object_store = couchdb.CouchDBObjectStore(TEST_CONFIG['couchdb']['url'],
+                                                       TEST_CONFIG['couchdb']['database'])
+        couchdb.register_credentials(TEST_CONFIG["couchdb"]["url"],
+                                     TEST_CONFIG["couchdb"]["user"],
+                                     TEST_CONFIG["couchdb"]["password"])
+        backends.register_backend("couchdb", couchdb.CouchDBBackend)
+        self.object_store.check_database()
+
+    def tearDown(self) -> None:
+        self.object_store.clear()
+
+    def test_object_store_add(self):
+        test_object = create_example_submodel()
+        self.object_store.add(test_object)
+        self.assertEqual(test_object.source, source_core+"IRI-https%3A%2F%2Facplt.org%2FTest_Submodel")
+
+    def test_retrieval(self):
+        test_object = create_example_submodel()
+        self.object_store.add(test_object)
+
+        # When retrieving the object, we should get the *same* instance as we added
+        test_object_retrieved = self.object_store.get_identifiable(
+            model.Identifier(id_='https://acplt.org/Test_Submodel', id_type=model.IdentifierType.IRI))
+        self.assertIs(test_object, test_object_retrieved)
+
+        # When retrieving it again, we should still get the same object
+        del test_object
+        test_object_retrieved_again = self.object_store.get_identifiable(
+            model.Identifier(id_='https://acplt.org/Test_Submodel', id_type=model.IdentifierType.IRI))
+        self.assertIs(test_object_retrieved, test_object_retrieved_again)
+
+        # However, a changed source should invalidate the cached object, so we should get a new copy
+        test_object_retrieved.source = "couchdb://example.com/example/IRI-https%3A%2F%2Facplt.org%2FTest_Submodel"
+        test_object_retrieved_third = self.object_store.get_identifiable(
+            model.Identifier(id_='https://acplt.org/Test_Submodel', id_type=model.IdentifierType.IRI))
+        self.assertIsNot(test_object_retrieved, test_object_retrieved_third)
+
+    def test_example_submodel_storing(self) -> None:
+        example_submodel = create_example_submodel()
+
+        # Add exmaple submodel
+        self.object_store.add(example_submodel)
+        self.assertEqual(1, len(self.object_store))
+        self.assertIn(example_submodel, self.object_store)
+
+        # Restore example submodel and check data
+        submodel_restored = self.object_store.get_identifiable(
+            model.Identifier(id_='https://acplt.org/Test_Submodel', id_type=model.IdentifierType.IRI))
+        assert (isinstance(submodel_restored, model.Submodel))
+        checker = AASDataChecker(raise_immediately=True)
+        check_example_submodel(checker, submodel_restored)
+
+        # Delete example submodel
+        self.object_store.discard(submodel_restored)
+        self.assertNotIn(example_submodel, self.object_store)
+
+    def test_iterating(self) -> None:
+        example_data = create_full_example()
+
+        # Add all objects
+        for item in example_data:
+            self.object_store.add(item)
+
+        self.assertEqual(6, len(self.object_store))
+
+        # Iterate objects, add them to a DictObjectStore and check them
+        retrieved_data_store: model.provider.DictObjectStore[model.Identifiable] = model.provider.DictObjectStore()
+        for item in self.object_store:
+            retrieved_data_store.add(item)
+        checker = AASDataChecker(raise_immediately=True)
+        check_full_example(checker, retrieved_data_store)
+
+    def test_key_errors(self) -> None:
+        # Double adding an object should raise a KeyError
+        example_submodel = create_example_submodel()
+        self.object_store.add(example_submodel)
+        with self.assertRaises(KeyError) as cm:
+            self.object_store.add(example_submodel)
+        self.assertEqual("'Identifiable with id Identifier(IRI=https://acplt.org/Test_Submodel) already exists in "
+                         "CouchDB database'", str(cm.exception))
+
+        # Querying a deleted object should raise a KeyError
+        retrieved_submodel = self.object_store.get_identifiable(
+            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
+        self.object_store.discard(example_submodel)
+        with self.assertRaises(KeyError) as cm:
+            self.object_store.get_identifiable(model.Identifier('https://acplt.org/Test_Submodel',
+                                                                model.IdentifierType.IRI))
+        self.assertEqual("'No Identifiable with id IRI-https://acplt.org/Test_Submodel found in CouchDB database'",
+                         str(cm.exception))
+
+        # Double deleting should also raise a KeyError
+        with self.assertRaises(KeyError) as cm:
+            self.object_store.discard(retrieved_submodel)
+        self.assertEqual("'No AAS object with id Identifier(IRI=https://acplt.org/Test_Submodel) exists in "
+                         "CouchDB database'", str(cm.exception))
+
+    def test_conflict_errors(self):
+        # Preperation: add object and retrieve it from the database
+        example_submodel = create_example_submodel()
+        self.object_store.add(example_submodel)
+        retrieved_submodel = self.object_store.get_identifiable(
+            model.Identifier('https://acplt.org/Test_Submodel', model.IdentifierType.IRI))
+
+        # Simulate a concurrent modification (Commit submodel, while preventing that the couchdb revision store is
+        # updated)
+        with unittest.mock.patch("aas.backend.couchdb.set_couchdb_revision"):
+            retrieved_submodel.commit()
+
+        # Committing changes to the retrieved object should now raise a conflict error
+        retrieved_submodel.id_short = "myOtherNewIdShort"
+        with self.assertRaises(couchdb.CouchDBConflictError) as cm:
+            retrieved_submodel.commit()
+        self.assertEqual("Could not commit changes to id Identifier(IRI=https://acplt.org/Test_Submodel) due to a "
+                         "concurrent modification in the database.", str(cm.exception))
+
+        # Deleting the submodel with safe_delete should also raise a conflict error. Deletion without safe_delete should
+        # work
+        with self.assertRaises(couchdb.CouchDBConflictError) as cm:
+            self.object_store.discard(retrieved_submodel, True)
+        self.assertEqual("Object with id Identifier(IRI=https://acplt.org/Test_Submodel) has been modified in the "
+                         "database since the version requested to be deleted.", str(cm.exception))
+        self.object_store.discard(retrieved_submodel, False)
+        self.assertEqual(0, len(self.object_store))
+
+        # Committing after deletion should not raise a conflict error due to removal of the source attribute
+        retrieved_submodel.commit()
+
+    def test_editing(self):
+        test_object = create_example_submodel()
+        self.object_store.add(test_object)
+
+        # Test if commit uploads changes
+        test_object.id_short = "SomeNewIdShort"
+        test_object.commit()
+
+        # Test if update restores changes
+        test_object.id_short = "AnotherIdShort"
+        test_object.update()
+        self.assertEqual("SomeNewIdShort", test_object.id_short)
diff --git a/test/model/test_base.py b/test/model/test_base.py
index 8adf0eff9c837bb16a532eecdfc0f931b15d8b34..dc0a59ad2ed40e52a930ca975c51f4a497b01dd0 100644
--- a/test/model/test_base.py
+++ b/test/model/test_base.py
@@ -13,7 +13,8 @@ import unittest
 from unittest import mock
 from typing import Optional, List
 
-from aas import model, backends
+from aas import model
+from aas.backend import backends
 from aas.model import Identifier, Identifiable