diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index b785b246ef0bc9a208c56fff416ec7c350ae5869..d12d224a27aa8b9ba2850dae75bf64de55869155 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -30,6 +30,9 @@ RUN apt-get update \
     && rm -Rf /usr/share/doc && rm -Rf /usr/share/man \
     && apt-get clean
 
+# Install gnmic
+RUN bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
+
 # Install oh-my-zsh for more terminal features and set it as primary shell
 ENV SHELL /bin/zsh
 RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true
diff --git a/.gitlab/ci/.test.yml b/.gitlab/ci/.test.yml
index 2b291b8f4d32e65f9701ce46d4e051df14622357..4d952f5bac1cfa3ce07148610b12f8563805b012 100644
--- a/.gitlab/ci/.test.yml
+++ b/.gitlab/ci/.test.yml
@@ -11,6 +11,7 @@ integration-test-gosdn:
         MONGO_INITDB_ROOT_PASSWORD: example
         INTEGRATION_TEST_TARGET_A: gnmi-target_A:7030
         INTEGRATION_TEST_TARGET_B: gnmi-target_B:7030
+        RABBITMQ_HOSTNAME: rabbitmq
     services:
       - name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/mongo:5
         alias: mongo
@@ -39,6 +40,7 @@ integration-test-gosdn:
         # Remove start of goSDN later when fixed. See: https://code.fbi.h-da.de/danet/gosdn/-/issues/335
         - cp -r artifacts/ssl/gosdn/certs artifacts/ssl/
         - cp -r artifacts/ssl/gosdn/private artifacts/ssl/
+        - mkdir artifacts/configs && cp controller/configs/gNMISubscriptions.txt.example artifacts/configs/gNMISubscriptions.txt
         - make build-gosdn
         - cd artifacts && GOSDN_ADMIN_PASSWORD=TestPassword ./gosdn --config ../controller/configs/integration-test-gosdn.toml --security secure &
         - INTEGRATION_TEST_CONTROLLER_URL="localhost:55055" go test -p 1 ./integration-tests/*
diff --git a/application-framework/registration/registration.go b/application-framework/registration/registration.go
index 3cb1827ce5a513719c37aa68aa5ffb9de32d3d32..10305ba5c517a06329157240be9534d96fb3d74a 100644
--- a/application-framework/registration/registration.go
+++ b/application-framework/registration/registration.go
@@ -10,13 +10,12 @@ import (
 )
 
 // Register registers a new app at the control plane.
-func Register(gosdnAddress, name, token string) (string, error) {
+func Register(ctx context.Context, gosdnAddress, name, token string) (string, error) {
 	conn, err := grpc.Dial(gosdnAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	if err != nil {
 		return "", err
 	}
 
-	ctx := context.Background()
 	appService := app.NewAppServiceClient(conn)
 
 	request := &app.AppRegisterRequest{
diff --git a/applications/arista-routing-engine/main.go b/applications/arista-routing-engine/main.go
index 7963c24ebbf47f475742c360b90036d66f08ea57..0ab37ca61d9964b846439c6bd7723fd2eb5537e6 100644
--- a/applications/arista-routing-engine/main.go
+++ b/applications/arista-routing-engine/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"os"
 
 	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
@@ -9,7 +10,7 @@ import (
 )
 
 func main() {
-	queueCredentials, err := registration.Register("localhost:55055", "arista-routing-engine", "SecurePresharedToken")
+	queueCredentials, err := registration.Register(context.Background(), "localhost:55055", "arista-routing-engine", "SecurePresharedToken")
 	if err != nil {
 		logrus.Errorf("failed to register application on control plane. %v", err)
 		os.Exit(1)
diff --git a/applications/basic-interface-monitoring/main.go b/applications/basic-interface-monitoring/main.go
index 5f451c99a2287e7be7a3e3cd46b42c6dc39d6f5e..676ee5f36aac4a245cf256f419a89dd620c68310 100644
--- a/applications/basic-interface-monitoring/main.go
+++ b/applications/basic-interface-monitoring/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
 
@@ -18,7 +19,7 @@ func main() {
 
 	flag.Parse()
 
-	queueCredentials, err := registration.Register(controllerAddress, "basic-interface-monitoring", "SecurePresharedToken")
+	queueCredentials, err := registration.Register(context.Background(), controllerAddress, "basic-interface-monitoring", "SecurePresharedToken")
 	if err != nil {
 		logrus.Errorf("failed to register application on control plane. %v", err)
 		os.Exit(1)
diff --git a/applications/hostname-checker/main.go b/applications/hostname-checker/main.go
index 53c3e462e2012a3361148b41525cde678fa97292..0dbcaabd5659d4821017f3b295c2e1c27eeb3a95 100644
--- a/applications/hostname-checker/main.go
+++ b/applications/hostname-checker/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"os"
 
 	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
@@ -9,7 +10,7 @@ import (
 )
 
 func main() {
-	queueCredentials, err := registration.Register("localhost:55055", "hostname-checker", "SecurePresharedToken")
+	queueCredentials, err := registration.Register(context.Background(), "localhost:55055", "hostname-checker", "SecurePresharedToken")
 	if err != nil {
 		logrus.Errorf("failed to register application on control plane. %v", err)
 		os.Exit(1)
diff --git a/applications/ws-events/main.go b/applications/ws-events/main.go
index 74131c99192356f6090a4555325ae1b949597b52..54565d568a06d36bb97c0c3f5ed8b1a3eb81d728 100644
--- a/applications/ws-events/main.go
+++ b/applications/ws-events/main.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"os"
 
@@ -14,7 +15,7 @@ func main() {
 	flag.Parse()
 
 	// Register the application at the controller
-	queueCredentials, err := registration.Register(*controllerAddress, "ws-events", "SecurePresharedToken")
+	queueCredentials, err := registration.Register(context.Background(), *controllerAddress, "ws-events", "SecurePresharedToken")
 	if err != nil {
 		logrus.Errorf("failed to register application on control plane. %v", err)
 		os.Exit(1)
diff --git a/dev_env_data/docker-compose/integration-test_docker-compose.yml b/dev_env_data/docker-compose/integration-test_docker-compose.yml
index 9d5ba0cb0ba554b36eee75c90f0196f9daab3e1c..8a8b6dd70c2f88064e73eae5a7184ec88e5858fb 100644
--- a/dev_env_data/docker-compose/integration-test_docker-compose.yml
+++ b/dev_env_data/docker-compose/integration-test_docker-compose.yml
@@ -19,6 +19,9 @@ services:
 
   rabbitmq:
     image: rabbitmq:3-management
+    ports:
+        - 127.0.0.1:5672:5672
+        - 127.0.0.1:15672:15672
     healthcheck:
         test: rabbitmq-diagnostics -q ping
         interval: 30s
diff --git a/integration-tests/application_tests/appUtility_test.go b/integration-tests/application_tests/appUtility_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4539072576150b1742635ed42307cfa17cfa0da2
--- /dev/null
+++ b/integration-tests/application_tests/appUtility_test.go
@@ -0,0 +1,102 @@
+package integration_test_application
+
+import (
+	"context"
+	"os"
+	"os/signal"
+	"strings"
+	"syscall"
+
+	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
+	"code.fbi.h-da.de/danet/gosdn/application-framework/registration"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc"
+)
+
+const localhost = "127.0.0.1"
+
+// Application is an example for a sdn application.
+type Application struct {
+	eventService          event.ServiceInterface
+	stopChannel           chan os.Signal
+	grpcClientConn        *grpc.ClientConn
+	addEventChannel       chan event.Event
+	updateEventChannel    chan event.Event
+	deleteEventChannel    chan event.Event
+	subscribeEventChannel chan event.Event
+}
+
+func NewApplication(ctx context.Context, grpcClientConn *grpc.ClientConn, controllerAddress string, topics []event.Topic, rabbitMQAddress string) *Application {
+	queueCredentials, err := registration.Register(ctx, controllerAddress, "integration-test-application", "SecurePresharedToken")
+	if err != nil {
+		logrus.Errorf("failed to register application on control plane. %v", err)
+		os.Exit(1)
+	}
+
+	if rabbitMQAddress != "" {
+		queueCredentials = strings.ReplaceAll(queueCredentials, localhost, rabbitMQAddress)
+	}
+
+	eventService, err := event.NewEventService(
+		queueCredentials,
+		topics,
+	)
+	if err != nil {
+		logrus.Errorf("failed to create event service. %v", err)
+		os.Exit(1)
+	}
+
+	return &Application{
+		eventService:          eventService,
+		stopChannel:           make(chan os.Signal, 1),
+		grpcClientConn:        grpcClientConn,
+		addEventChannel:       make(chan event.Event, 1),
+		updateEventChannel:    make(chan event.Event, 1),
+		deleteEventChannel:    make(chan event.Event, 1),
+		subscribeEventChannel: make(chan event.Event, 1),
+	}
+}
+
+// Run runs the application.
+func (a *Application) Run(eventTypeCallbackTuples []event.TypeToCallbackTuple) {
+	signal.Notify(a.stopChannel, os.Interrupt, syscall.SIGTERM)
+
+	a.eventService.SubscribeToEventType(eventTypeCallbackTuples)
+	a.eventService.SetupEventReciever(a.stopChannel)
+
+	var forever chan struct{}
+
+	go func() {
+		for {
+			select {
+			case <-a.stopChannel:
+				close(forever)
+				_ = a.grpcClientConn.Close()
+
+				return
+			}
+		}
+	}()
+
+	<-forever
+}
+
+func (a *Application) addCallback(event *event.Event) {
+	logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
+	a.addEventChannel <- *event
+}
+
+func (a *Application) updateCallback(event *event.Event) {
+	logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
+	a.updateEventChannel <- *event
+}
+
+func (a *Application) deleteCallback(event *event.Event) {
+	logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
+	a.deleteEventChannel <- *event
+}
+
+func (a *Application) subscribeCallback(event *event.Event) {
+	logrus.Infof("Incoming Event: EntityID: %v, ID: %v, PathsAndValues: %v, Type: %v", event.EntityID, event.ID, event.PathsAndValuesMap, event.Type)
+	a.subscribeEventChannel <- *event
+}
diff --git a/integration-tests/application_tests/application_test.go b/integration-tests/application_tests/application_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..721bd2cfa770f6a30826e75e49ddeb907a849e0f
--- /dev/null
+++ b/integration-tests/application_tests/application_test.go
@@ -0,0 +1,305 @@
+package integration_test_application
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"testing"
+
+	"code.fbi.h-da.de/danet/gosdn/api/go/gosdn/conflict"
+	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
+	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
+	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/transport"
+	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
+	integration_test_utils "code.fbi.h-da.de/danet/gosdn/integration-tests/integrationTestUtils"
+
+	"google.golang.org/grpc"
+)
+
+// Notes:
+// When adding tests take into account all the messages that get added to the message
+// queues during test setups and actual test cases. So make sure to read from the according eventChannels
+// of the application.
+//
+// Do not reset test setup in this because it causes weird interactions between test cases and RabbitMQ message queues.
+// Reason for that is that cleanup creates messages to be put into the queues of RabbitMQ.
+
+const targetAAdress = "gnmi-target_A:7030"
+const targetUserAndPW = "admin"
+const pndID = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
+const pluginID = "d1c269a2-6482-4010-b0d8-679dff73153b"
+const mneID = "9ee7bf15-15ff-44b8-aafa-035487d1e97f"
+
+const userID = "23224223-aeb6-433b-a797-2d671d7424f0"
+const roleID = "679f7982-d740-452e-b78d-c1d133233694"
+
+// The connection to the controller to use in each test.
+var conn *grpc.ClientConn
+
+// The context containing the credentials when authenticated.
+var ctx context.Context
+
+// A defaultSDN config with default/empty values.
+var defaultSDNConfig string
+
+var application *Application
+
+func TestMain(m *testing.M) {
+	localConn, localCtx, err := integration_test_utils.CreateSecureConnection()
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+	conn = localConn
+	ctx = localCtx
+
+	sndConfig, err := integration_test_utils.ExportCurrentSDNConfig(conn, ctx)
+	defaultSDNConfig = sndConfig
+	if err != nil {
+		fmt.Println(err.Error())
+	}
+
+	topics := []event.Topic{event.ManagedNetworkElement, event.User, event.Role}
+
+	rabbitMQAddress := ""
+	envVarRabbitmq := os.Getenv("RABBITMQ_HOSTNAME")
+	if envVarRabbitmq != "" {
+		rabbitMQAddress = envVarRabbitmq
+	}
+
+	application = NewApplication(ctx, conn, ":55055", topics, rabbitMQAddress)
+
+	eventTypeCallbackTuples := []event.TypeToCallbackTuple{
+		{
+			Type:     event.Add,
+			Callback: application.addCallback,
+		},
+		{
+			Type:     event.Update,
+			Callback: application.updateCallback,
+		},
+		{
+			Type:     event.Delete,
+			Callback: application.deleteCallback,
+		},
+		{
+			Type:     event.Subscribe,
+			Callback: application.subscribeCallback,
+		},
+	}
+	go application.Run(eventTypeCallbackTuples)
+
+	// This is needed to clear the go channel of the messages sent by RabbitMQ when creating
+	// and logging in with the admin user.
+	// Important note: only works once after starting the setup, because first time use creates
+	// a user and role and update the user because of the login. After then only logins are done, no user and role creations.
+	// This means that this will block after trying once, because of the three attempts to read from eventChannels.
+
+	_ = <-application.addEventChannel
+	_ = <-application.addEventChannel
+	_ = <-application.updateEventChannel
+
+	m.Run()
+}
+
+func TestNetworkElementAddAndSubscribeEvent(t *testing.T) {
+	// setup required parameters
+	opt := &tpb.TransportOption{
+		Address:  targetAAdress,
+		Username: targetUserAndPW,
+		Password: targetUserAndPW,
+		TransportOption: &tpb.TransportOption_GnmiTransportOption{
+			GnmiTransportOption: &tpb.GnmiTransportOption{},
+		},
+		Tls: true,
+	}
+
+	addListRequest := &mnepb.AddListRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		Mne: []*mnepb.SetMne{
+			{
+				MneId:           mneID,
+				Address:         "gnmi-target_A:7030",
+				Pid:             pndID,
+				PluginId:        pluginID,
+				MneName:         "Horst",
+				TransportOption: opt,
+			},
+		},
+		Pid: pndID,
+	}
+
+	deleteMneRequest := &mnepb.DeleteRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		Pid:       pndID,
+		Mneid:     mneID,
+	}
+
+	// setup gRPC services
+	mneService := mnepb.NewNetworkElementServiceClient(conn)
+
+	// add one device to the controller
+	_, err := mneService.AddList(ctx, addListRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	// check if events are available and correct type and content
+	addEvent := <-application.addEventChannel
+	assertAddEvent(t, addEvent, mneID)
+
+	subscribeEvent := <-application.subscribeEventChannel
+	assertSubscribeEvent(t, subscribeEvent, mneID)
+
+	subscribeEvent = <-application.subscribeEventChannel
+	assertSubscribeEvent(t, subscribeEvent, mneID)
+
+	// TODO: add tests fot rhis event type when update function is implemented properly
+	// update mne and check events
+
+	// delete mne and check for events
+	_, err = mneService.Delete(ctx, deleteMneRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	deleteEvent := <-application.deleteEventChannel
+	assertDeleteEvent(t, deleteEvent, mneID)
+}
+
+func TestUserEvents(t *testing.T) {
+	// setup required parameters
+	const newUserName = "new name"
+
+	addUserRequest := &apb.CreateUsersRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		User: []*apb.User{
+			{
+				Id:       userID,
+				Name:     "user",
+				Roles:    map[string]string{pndID: "admin"},
+				Password: targetUserAndPW,
+				Metadata: &conflict.Metadata{
+					ResourceVersion: 0,
+				},
+			},
+		},
+	}
+
+	updateUserRequest := &apb.UpdateUsersRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		User: []*apb.UpdateUser{
+			{
+				Id:       userID,
+				Name:     newUserName,
+				Metadata: &conflict.Metadata{},
+			},
+		},
+	}
+
+	deleteUserRequest := &apb.DeleteUsersRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		Username:  []string{newUserName},
+	}
+
+	// setup gRPC services
+	userService := apb.NewUserServiceClient(conn)
+
+	// add one device to the controller
+	_, err := userService.CreateUsers(ctx, addUserRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	// check if event is available and correct type
+	addEvent := <-application.addEventChannel
+	assertAddEvent(t, addEvent, userID)
+
+	// update user and check for event
+	_, err = userService.UpdateUsers(ctx, updateUserRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	updateEvent := <-application.updateEventChannel
+	assertUpdateEvent(t, updateEvent, userID)
+
+	// delete user and check for event
+	_, err = userService.DeleteUsers(ctx, deleteUserRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	deleteEvent := <-application.deleteEventChannel
+	assertDeleteEvent(t, deleteEvent, userID)
+}
+
+func TestRoleEvents(t *testing.T) {
+	// setup required parameters
+	const roleName = "new role"
+
+	addRoleRequest := &apb.CreateRolesRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		Roles: []*apb.Role{
+			{
+				Id:          roleID,
+				Name:        roleName,
+				Description: "well, not much to see here",
+				Permissions: []string{"some permission"},
+			},
+		},
+	}
+
+	updateRolesRequest := &apb.UpdateRolesRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		Roles: []*apb.Role{
+			{
+				Id:          roleID,
+				Name:        roleName,
+				Description: "even less now",
+			},
+		},
+	}
+
+	deleteRoleRequest := &apb.DeleteRolesRequest{
+		Timestamp: integration_test_utils.GetTimestamp(),
+		RoleName:  []string{roleName},
+	}
+
+	// setup gRPC services
+	roleService := apb.NewRoleServiceClient(conn)
+
+	// add role and check add event
+	_, err := roleService.CreateRoles(ctx, addRoleRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	addEvent := <-application.addEventChannel
+	assertAddEvent(t, addEvent, roleID)
+
+	// update role and check for event
+	_, err = roleService.UpdateRoles(ctx, updateRolesRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	updateEvent := <-application.updateEventChannel
+	assertUpdateEvent(t, updateEvent, roleID)
+
+	// delete new role and check for event
+	_, err = roleService.DeleteRoles(ctx, deleteRoleRequest)
+	if err != nil {
+		t.Error(err)
+		t.FailNow()
+	}
+
+	deleteEvent := <-application.deleteEventChannel
+	assertDeleteEvent(t, deleteEvent, roleID)
+}
diff --git a/integration-tests/application_tests/eventAssertion_test.go b/integration-tests/application_tests/eventAssertion_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6bf363b248f609d391fadaaeb0458353ee0fb4dd
--- /dev/null
+++ b/integration-tests/application_tests/eventAssertion_test.go
@@ -0,0 +1,26 @@
+package integration_test_application
+
+import (
+	"code.fbi.h-da.de/danet/gosdn/application-framework/event"
+	"github.com/stretchr/testify/assert"
+)
+
+func assertAddEvent(t assert.TestingT, addEvent event.Event, ID string) {
+	assert.Equal(t, event.Add.String(), addEvent.Type)
+	assert.Equal(t, ID, addEvent.EntityID.String())
+}
+
+func assertUpdateEvent(t assert.TestingT, updateEvent event.Event, ID string) {
+	assert.Equal(t, event.Update.String(), updateEvent.Type)
+	assert.Equal(t, ID, updateEvent.EntityID.String())
+}
+
+func assertDeleteEvent(t assert.TestingT, deleteEvent event.Event, ID string) {
+	assert.Equal(t, event.Delete.String(), deleteEvent.Type)
+	assert.Equal(t, ID, deleteEvent.EntityID.String())
+}
+
+func assertSubscribeEvent(t assert.TestingT, subscribeEvent event.Event, ID string) {
+	assert.Equal(t, event.Subscribe.String(), subscribeEvent.Type)
+	assert.Equal(t, ID, subscribeEvent.EntityID.String())
+}
diff --git a/integration-tests/example_tests/example_test.go b/integration-tests/example_tests/example_test.go
index b97e86fa85c09004d7dd6167e880a8e9a5c9b51e..03f52081b1fd0c72be24cb0ae74cc4411a899537 100644
--- a/integration-tests/example_tests/example_test.go
+++ b/integration-tests/example_tests/example_test.go
@@ -31,6 +31,9 @@ func TestMain(m *testing.M) {
 	if err != nil {
 		fmt.Println(err.Error())
 	}
+
+	integration_test_utils.ApplySDNConfig(conn, ctx, defaultSDNConfig)
+
 	m.Run()
 }