Repository: hopeit-git/hopeit.engine (language: python)

Issue:
feature(engine): startup only selected groups of events
## Motivation
Now when starting a hopeit app, using `hopeit_server` cli, it will start all events including endpoints and streams (with `--start-streams` specified).

Sometimes it is needed to start different events (especially for stream processing) in separated deployments/runtimes. 

## Solution
Enable `hopeit_server` cli to start a defined list of events, based on a `groups` label specified in the application config file.
```
"events": {
  "event1": {
    "type": "GET",
    "group": "group1"
  },
  "event2": {
    "type": "STREAM",
    "group": "group1"
  },
  "event3": {
    "type": "STREAM",
    "group": "group2"
  },
  "event4": {
    "type": "POST",
    "group": "group2"
  },
 "healthcheck": {
   "type": "GET
 }
}
```

Then:
`hopeit_server --groups=group1 ...` will starts `event1`, `event2` and  `healthcheck`  in the current instance, when an event like `healthcheck` has no groups specified it will by asumed  as `group: 'DEFAULT'` Note: events from group `DEFAULT` allway start.
`hopeit_server --groups=group2 ...` will start `event3` ,`event4` and  `healthcheck`
`hopeit_server --groups=group2,DEFAULT` will start `event3`, `event4` and `healthcheck` events, same as before line.
`hopeit_server --groups=DEFAULT` will start only `healthcheck`, when an event like `healthcheck` has no groups specified it will by asumed  as `group: 'DEFAULT'`
`hopeit_server ....` without group parameters, will start all events.

#### Default behavior / backward compatibility
When `hopeit_server` is started with no `--groups` parameters, ALL events will be started.
When no group is specified for an event, it will be assumed as `DEFAULT` group, i.e.: `group: 'DEFAULT'`


Discussion / hints:

Fix patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5918962b..ddb019c5 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,5 +1,5 @@
 {
-    "version": "0.2.0",
+    "version": "0.2.0",    
     "configurations": [
         {
             "name": "simple-example",
@@ -10,7 +10,8 @@
                 "--port=8020",
                 "--start-streams",
                 "--config-files=engine/config/dev-local.json,plugins/auth/basic-auth/config/plugin-config.json,plugins/ops/config-manager/config/plugin-config.json,apps/examples/simple-example/config/app-config.json",
-                "--api-file=apps/examples/simple-example/api/openapi.json"
+                "--api-file=apps/examples/simple-example/api/openapi.json",
+                "--enabled-groups=g1,g2"
             ],
             "cwd": "${workspaceFolder}",
             "console": "integratedTerminal"
diff --git a/apps/benchmark/simple-benchmark/api/openapi.json b/apps/benchmark/simple-benchmark/api/openapi.json
index 65f49330..c1a1b069 100644
--- a/apps/benchmark/simple-benchmark/api/openapi.json
+++ b/apps/benchmark/simple-benchmark/api/openapi.json
@@ -6,7 +6,7 @@
     "description": "Simple Benchmark App"
   },
   "paths": {
-    "/api/simple-benchmark/0x15/give-me-something": {
+    "/api/simple-benchmark/0x16/give-me-something": {
       "get": {
         "summary": "Simple Benchamrck: Give me Something",
         "description": "Loads Something from disk",
@@ -53,11 +53,11 @@
           }
         },
         "tags": [
-          "simple_benchmark.0x15"
+          "simple_benchmark.0x16"
         ]
       }
     },
-    "/api/simple-benchmark/0x15/query-something-fs": {
+    "/api/simple-benchmark/0x16/query-something-fs": {
       "get": {
         "summary": "Simple Benchamrck: Query Something",
         "description": "Loads Something from disk",
@@ -114,11 +114,11 @@
           }
         },
         "tags": [
-          "simple_benchmark.0x15"
+          "simple_benchmark.0x16"
         ]
       }
     },
-    "/api/simple-benchmark/0x15/save-something-fs": {
+    "/api/simple-benchmark/0x16/save-something-fs": {
       "post": {
         "summary": "Simple Benchmark: Save Something",
         "description": "Creates and saves Something",
@@ -176,11 +176,11 @@
           }
         },
         "tags": [
-          "simple_benchmark.0x15"
+          "simple_benchmark.0x16"
         ]
       }
     },
-    "/api/simple-benchmark/0x15/query-something-redis": {
+    "/api/simple-benchmark/0x16/query-something-redis": {
       "get": {
         "summary": "Simple Benchamrck: Query Something",
         "description": "Loads Something from disk",
@@ -237,11 +237,11 @@
           }
         },
         "tags": [
-          "simple_benchmark.0x15"
+          "simple_benchmark.0x16"
         ]
       }
     },
-    "/api/simple-benchmark/0x15/save-something-redis": {
+    "/api/simple-benchmark/0x16/save-something-redis": {
       "post": {
         "summary": "Simple Benchmark: Save Something",
         "description": "Creates and saves Something",
@@ -299,7 +299,7 @@
           }
         },
         "tags": [
-          "simple_benchmark.0x15"
+          "simple_benchmark.0x16"
         ]
       }
     }
diff --git a/apps/examples/client-example/api/openapi.json b/apps/examples/client-example/api/openapi.json
index 084e686b..eee2800a 100644
--- a/apps/examples/client-example/api/openapi.json
+++ b/apps/examples/client-example/api/openapi.json
@@ -1,12 +1,12 @@
 {
   "openapi": "3.0.3",
   "info": {
-    "version": "0.15",
+    "version": "0.16",
     "title": "Client Example",
     "description": "Client Example"
   },
   "paths": {
-    "/api/config-manager/0x15/runtime-apps-config": {
+    "/api/config-manager/0x16/runtime-apps-config": {
       "get": {
         "summary": "Config Manager: Runtime Apps Config",
         "description": "Returns the runtime config for the Apps running on this server",
@@ -62,11 +62,11 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
-    "/api/config-manager/0x15/cluster-apps-config": {
+    "/api/config-manager/0x16/cluster-apps-config": {
       "get": {
         "summary": "Config Manager: Cluster Apps Config",
         "description": "Handle remote access to runtime configuration for a group of hosts",
@@ -122,11 +122,11 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
-    "/api/client-example/0x15/count-and-save": {
+    "/api/client-example/0x16/count-and-save": {
       "get": {
         "summary": "Client Example: Count Objects and Save new one",
         "description": "Count all available Something objects connecting to simple-example app",
@@ -193,7 +193,7 @@
           }
         },
         "tags": [
-          "client_example.0x15"
+          "client_example.0x16"
         ],
         "security": [
           {
@@ -526,10 +526,14 @@
               "type": "string"
             },
             "default": []
+          },
+          "group": {
+            "type": "string",
+            "default": "DEFAULT"
           }
         },
         "x-module-name": "hopeit.app.config",
-        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    "
+        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    :field: group, str: group name, if none is assigned it is automatically assigned as 'DEFAULT'.\n    "
       },
       "EventConnection": {
         "type": "object",
@@ -656,7 +660,7 @@
           },
           "engine_version": {
             "type": "string",
-            "default": "0.15.1"
+            "default": "0.16.0"
           }
         },
         "x-module-name": "hopeit.server.config",
diff --git a/apps/examples/simple-example/api/openapi.json b/apps/examples/simple-example/api/openapi.json
index 533139f5..6a739b9a 100644
--- a/apps/examples/simple-example/api/openapi.json
+++ b/apps/examples/simple-example/api/openapi.json
@@ -1,12 +1,12 @@
 {
   "openapi": "3.0.3",
   "info": {
-    "version": "0.15",
+    "version": "0.16",
     "title": "Simple Example",
     "description": "Simple Example"
   },
   "paths": {
-    "/api/basic-auth/0x15/decode": {
+    "/api/basic-auth/0x16/decode": {
       "get": {
         "summary": "Basic Auth: Decode",
         "description": "Returns decoded auth info",
@@ -44,7 +44,7 @@
           }
         },
         "tags": [
-          "basic_auth.0x15"
+          "basic_auth.0x16"
         ],
         "security": [
           {
@@ -53,7 +53,7 @@
         ]
       }
     },
-    "/api/config-manager/0x15/runtime-apps-config": {
+    "/api/config-manager/0x16/runtime-apps-config": {
       "get": {
         "summary": "Config Manager: Runtime Apps Config",
         "description": "Returns the runtime config for the Apps running on this server",
@@ -109,11 +109,11 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
-    "/api/config-manager/0x15/cluster-apps-config": {
+    "/api/config-manager/0x16/cluster-apps-config": {
       "get": {
         "summary": "Config Manager: Cluster Apps Config",
         "description": "Handle remote access to runtime configuration for a group of hosts",
@@ -169,11 +169,11 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
-    "/api/simple-example/0x15/list-somethings": {
+    "/api/simple-example/0x16/list-somethings": {
       "get": {
         "summary": "Simple Example: List Objects",
         "description": "Lists all available Something objects",
@@ -229,7 +229,7 @@
         ],
         "responses": {
           "200": {
-            "description": "List",
+            "description": "typing.List[model.Something]",
             "content": {
               "application/json": {
                 "schema": {
@@ -243,7 +243,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -252,7 +252,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/query-something": {
+    "/api/simple-example/0x16/query-something": {
       "get": {
         "summary": "Simple Example: Query Something",
         "description": "Loads Something from disk",
@@ -338,7 +338,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -442,7 +442,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -451,7 +451,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/save-something": {
+    "/api/simple-example/0x16/save-something": {
       "post": {
         "summary": "Simple Example: Save Something",
         "description": "Creates and saves Something",
@@ -548,7 +548,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -557,7 +557,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/download-something": {
+    "/api/simple-example/0x16/download-something": {
       "get": {
         "summary": "Simple Example: Download Something",
         "description": "Download image file. The PostprocessHook return the requested file as stream.",
@@ -644,7 +644,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -653,7 +653,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/download-something-streamed": {
+    "/api/simple-example/0x16/download-something-streamed": {
       "get": {
         "summary": "Simple Example: Download Something Streamed",
         "description": "Download streamd created content as file.\nThe PostprocessHook return the requested resource as stream using `prepare_stream_response`.",
@@ -721,11 +721,11 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ]
       }
     },
-    "/api/simple-example/0x15/upload-something": {
+    "/api/simple-example/0x16/upload-something": {
       "post": {
         "summary": "Simple Example: Multipart Upload files",
         "description": "Upload files using Multipart form request",
@@ -863,7 +863,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -872,7 +872,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/streams/something-event": {
+    "/api/simple-example/0x16/streams/something-event": {
       "post": {
         "summary": "Simple Example: Something Event",
         "description": "Submits a Something object to a stream to be processed asynchronously by process-events app event",
@@ -941,7 +941,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -950,7 +950,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/collector/query-concurrently": {
+    "/api/simple-example/0x16/collector/query-concurrently": {
       "post": {
         "summary": "Simple Example: Query Concurrently",
         "description": "Loads 2 Something objects concurrently from disk and combine the results\nusing `collector` steps constructor (instantiating an `AsyncCollector`)",
@@ -1022,7 +1022,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -1031,7 +1031,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/collector/collect-spawn": {
+    "/api/simple-example/0x16/collector/collect-spawn": {
       "post": {
         "summary": "Simple Example: Collect and Spawn",
         "description": "Loads 2 Something objects concurrently from disk, combine the results\nusing `collector` steps constructor (instantiating an `AsyncCollector`)\nthen spawn the items found individually into a stream",
@@ -1109,7 +1109,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -1118,7 +1118,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/shuffle/spawn-event": {
+    "/api/simple-example/0x16/shuffle/spawn-event": {
       "post": {
         "summary": "Simple Example: Spawn Event",
         "description": "This example will spawn 3 data events, those are going to be send to a stream using SHUFFLE\nand processed in asynchronously / in parallel if multiple nodes are available",
@@ -1196,7 +1196,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -1205,7 +1205,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/shuffle/parallelize-event": {
+    "/api/simple-example/0x16/shuffle/parallelize-event": {
       "post": {
         "summary": "Simple Example: Parallelize Event",
         "description": "This example will spawn 2 copies of payload data, those are going to be send to a stream using SHUFFLE\nand processed in asynchronously / in parallel if multiple nodes are available,\nthen submitted to other stream to be updated and saved",
@@ -1283,7 +1283,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -1292,7 +1292,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/basic-auth/0x15/login": {
+    "/api/simple-example/0x16/basic-auth/0x16/login": {
       "get": {
         "summary": "Basic Auth: Login",
         "description": "Handles users login using basic-auth\nand generate access tokens for external services invoking apps\nplugged in with basic-auth plugin.",
@@ -1360,7 +1360,7 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
@@ -1369,7 +1369,7 @@
         ]
       }
     },
-    "/api/simple-example/0x15/basic-auth/0x15/refresh": {
+    "/api/simple-example/0x16/basic-auth/0x16/refresh": {
       "get": {
         "summary": "Basic Auth: Refresh",
         "description": "This event can be used for obtain new access token and update refresh token (http cookie),\nwith no need to re-login the user if there is a valid refresh token active.",
@@ -1437,16 +1437,16 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
-            "simple_example.0x15.refresh": []
+            "simple_example.0x16.refresh": []
           }
         ]
       }
     },
-    "/api/simple-example/0x15/basic-auth/0x15/logout": {
+    "/api/simple-example/0x16/basic-auth/0x16/logout": {
       "get": {
         "summary": "Basic Auth: Logout",
         "description": "Invalidates previous refresh cookies.",
@@ -1523,11 +1523,11 @@
           }
         },
         "tags": [
-          "simple_example.0x15"
+          "simple_example.0x16"
         ],
         "security": [
           {
-            "simple_example.0x15.refresh": []
+            "simple_example.0x16.refresh": []
           }
         ]
       }
@@ -1918,10 +1918,14 @@
               "type": "string"
             },
             "default": []
+          },
+          "group": {
+            "type": "string",
+            "default": "DEFAULT"
           }
         },
         "x-module-name": "hopeit.app.config",
-        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    "
+        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    :field: group, str: group name, if none is assigned it is automatically assigned as 'DEFAULT'.\n    "
       },
       "EventConnection": {
         "type": "object",
@@ -2048,7 +2052,7 @@
           },
           "engine_version": {
             "type": "string",
-            "default": "0.15.1"
+            "default": "0.16.0"
           }
         },
         "x-module-name": "hopeit.server.config",
@@ -2309,10 +2313,10 @@
         "type": "http",
         "scheme": "bearer"
       },
-      "simple_example.0x15.refresh": {
+      "simple_example.0x16.refresh": {
         "type": "apiKey",
         "in": "cookie",
-        "name": "simple_example.0x15.refresh"
+        "name": "simple_example.0x16.refresh"
       }
     }
   },
diff --git a/apps/examples/simple-example/config/app-config.json b/apps/examples/simple-example/config/app-config.json
index e9e50bc2..ae8f7a3e 100644
--- a/apps/examples/simple-example/config/app-config.json
+++ b/apps/examples/simple-example/config/app-config.json
@@ -82,7 +82,8 @@
     },
     "save_something" : {
       "type": "POST",
-      "setting_keys": ["fs_storage"]
+      "setting_keys": ["fs_storage"],
+      "group": "g2"
     },
     "download_something": {
       "type": "GET"
@@ -92,7 +93,8 @@
       "auth" : ["Unsecured"]
     },
     "upload_something": {
-      "type": "MULTIPART"
+      "type": "MULTIPART",
+      "group": "g1"
     },
     "service.something_generator": {
       "type": "SERVICE",
diff --git a/docs/source/benchmark/dedicated.rst b/docs/source/benchmark/dedicated.rst
index b18160f2..12968af8 100644
--- a/docs/source/benchmark/dedicated.rst
+++ b/docs/source/benchmark/dedicated.rst
@@ -12,11 +12,11 @@ real time") for every request in the 99th percentile. The result is a rate (requ
 
 Three endpoints are evaluated in each test scenario
 
-* http://hostname:port/api/simple-benchmark/0x15/give-me-something retrieves information from objects randomly created
+* http://hostname:port/api/simple-benchmark/0x16/give-me-something retrieves information from objects randomly created
 
-* http://hostname:port/api/simple-benchmark/0x15/query-something-redis returns information from objects stored on a Redis server (memory store)
+* http://hostname:port/api/simple-benchmark/0x16/query-something-redis returns information from objects stored on a Redis server (memory store)
 
-* http://hostname:port/api/simple-benchmark/0x15/query-something-fs retrieves information from objects stored on a file system (SSD drive)
+* http://hostname:port/api/simple-benchmark/0x16/query-something-fs retrieves information from objects stored on a file system (SSD drive)
 
 Scenarios:
 __________
@@ -68,13 +68,13 @@ Now we are ready to start the simple-benchmark app to run the tests
 
 Latency (HdrHistogram - Uncorrected Latency, measured without taking delayed starts into account) results:
 
-./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/give-me-something?item_id=string"
+./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/give-me-something?item_id=string"
  p99 30.38ms @ 1794.29 req/s
 
-./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-redis?item_id=string"
+./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-redis?item_id=string"
  p99 27.18ms @ 1226.51 req/s
 
-./wrk2/wrk -t4 -c18 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-fs?item_id=string"
+./wrk2/wrk -t4 -c18 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-fs?item_id=string"
  p99 31.12ms @ 803.38 req/s
 
 2 . Docker, single/multiple instances of hopeit.engine
@@ -103,22 +103,22 @@ Latency (HdrHistogram - Uncorrected Latency, measured without taking delayed sta
 
 4 hopeit.engine instances behind a reverse proxy
 
-./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/give-me-something?item_id=string"
+./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/give-me-something?item_id=string"
  p99 27.77ms @ 2587.95 req/s
 
-./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-redis?item_id=string"
+./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-redis?item_id=string"
  p99 28.67ms @ 1810.14 req/s
 
-./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-fs?item_id=string"
+./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-fs?item_id=string"
  p99 30.29ms @ 1241.39 req/s
 
 1 hopeit.engine instance
 
-./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/give-me-something?item_id=string"
+./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/give-me-something?item_id=string"
  p99 26.48ms @ 1336.18 req/s
 
-./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-redis?item_id=string"
+./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-redis?item_id=string"
  p99 29.14ms @ 908.93 req/s
 
-./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-fs?item_id=string"
+./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-fs?item_id=string"
  p99 24.77ms @ 624.77 req/s
\ No newline at end of file
diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst
index 5806b606..12ed49fe 100644
--- a/docs/source/release-notes.rst
+++ b/docs/source/release-notes.rst
@@ -2,6 +2,13 @@ Release Notes
 =============
 
 
+Version 0.16.0
+______________
+Engine:
+- Support for event `group` attribute and start selected groupson engine
+- `hopeit_server` command line support `enabled_groups` parameter
+
+
 Version 0.15.1
 ______________
 - Add `payload_raw` property to PreprocessHook object
diff --git a/engine/config/schemas/app-config-schema-draftv6.json b/engine/config/schemas/app-config-schema-draftv6.json
index b2b8b88b..c36bdc86 100644
--- a/engine/config/schemas/app-config-schema-draftv6.json
+++ b/engine/config/schemas/app-config-schema-draftv6.json
@@ -216,6 +216,9 @@
         "route": {
           "type": "string"
         },
+        "impl": {
+          "type": "string"
+        },
         "connections": {
           "type": "array",
           "items": {
@@ -248,9 +251,20 @@
             "type": "string"
           },
           "default": []
+        },
+        "dataobjects": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "default": []
+        },
+        "group": {
+          "type": "string",
+          "default": "DEFAULT"
         }
       },
-      "description": "\n    Event descriptor\n    "
+      "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    :field: group, str: group name, if none is assigned it is automatically assigned as 'DEFAULT'.\n    "
     },
     "EventConnection": {
       "type": "object",
@@ -370,7 +384,7 @@
         },
         "engine_version": {
           "type": "string",
-          "default": "0.13.0"
+          "default": "0.16.0"
         }
       },
       "description": "\n    Server configuration\n    "
diff --git a/engine/config/schemas/server-config-schema-draftv6.json b/engine/config/schemas/server-config-schema-draftv6.json
index 1c508403..3ade5579 100644
--- a/engine/config/schemas/server-config-schema-draftv6.json
+++ b/engine/config/schemas/server-config-schema-draftv6.json
@@ -38,7 +38,7 @@
     },
     "engine_version": {
       "type": "string",
-      "default": "0.13.0"
+      "default": "0.16.0"
     }
   },
   "description": "\n    Server configuration\n    ",
diff --git a/engine/src/hopeit/app/config.py b/engine/src/hopeit/app/config.py
index e2a4be63..fa712188 100644
--- a/engine/src/hopeit/app/config.py
+++ b/engine/src/hopeit/app/config.py
@@ -324,7 +324,10 @@ class EventDescriptor:
         When not specified, the engine will inspect the module implementation and find all datatypes supported
         as payload in the functions defined as `__steps__`. In case of generic functions that support
         `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.
+    :field: group, str: group name, if none is assigned it is automatically assigned as 'DEFAULT'.
     """
+    DEFAULT_GROUP = 'DEFAULT'
+
     type: EventType
     plug_mode: EventPlugMode = EventPlugMode.STANDALONE
     route: Optional[str] = None
@@ -335,6 +338,7 @@ class EventDescriptor:
     auth: List[AuthType] = field(default_factory=list)
     setting_keys: List[str] = field(default_factory=list)
     dataobjects: List[str] = field(default_factory=list)
+    group: str = DEFAULT_GROUP
 
     def __post_init__(self):
         if self.read_stream:
diff --git a/engine/src/hopeit/cli/server.py b/engine/src/hopeit/cli/server.py
index 4dafcf0a..602fee38 100644
--- a/engine/src/hopeit/cli/server.py
+++ b/engine/src/hopeit/cli/server.py
@@ -26,12 +26,19 @@ def server():
 @click.option('--port', default='8020', help='TCP/IP port to listen.')
 @click.option('--path', help='POSIX complaint socket name.')
 @click.option('--start-streams', is_flag=True, default=False, help='Auto start reading stream events.')
-def run(config_files: str, api_file: str, host: str, port: int, path: str, start_streams: bool):
+@click.option('--enabled-groups', prompt='Comma-separated group labels.',
+              help="List of groups to start. If no group is specified, all events will be started."
+              "Events with DEFAULT or no group will always be started. DEAULT group label can be also"
+              "used explicitly to start only events with no group / default group.")
+def run(config_files: str, api_file: str, host: str, port: int, path: str, start_streams: bool, enabled_groups: str):
     """
     Runs web server hosting apps specified in config files.
     """
     web.prepare_engine(
-        config_files=config_files.split(','), api_file=api_file, start_streams=start_streams
+        config_files=config_files.split(','),
+        api_file=api_file,
+        enabled_groups=enabled_groups.split(','),
+        start_streams=start_streams,
     )
     web.serve(host=host, path=path, port=port)
 
diff --git a/engine/src/hopeit/server/engine.py b/engine/src/hopeit/server/engine.py
index fd73c201..239ef0cc 100644
--- a/engine/src/hopeit/server/engine.py
+++ b/engine/src/hopeit/server/engine.py
@@ -35,15 +35,19 @@ class AppEngine:
     Engine that handles a Hopeit Application
     """
 
-    def __init__(self, *, app_config: AppConfig, plugins: List[AppConfig], streams_enabled: bool = True):
+    def __init__(self, *, app_config: AppConfig, plugins: List[AppConfig],
+                 enabled_groups: List[str], streams_enabled: bool = True):
         """
         Creates an instance of the AppEngine
 
         :param app_config: AppConfig, Hopeit application configuration as specified in config module
+        :param plugins: List of AppConfig, Hopeit application configurations for enabled plugins
+        :enabled_groups: List of str, list of enabled event groups
+        :streams_enabled: bool, for testing, set to False to disable automatic starting streams
         """
         self.app_config = app_config
         self.app_key = app_config.app_key()
-        self.effective_events = self._config_effective_events(app_config)
+        self.effective_events = self._config_effective_events(app_config, enabled_groups)
         self.plugins = plugins
         self.settings = get_runtime_settings(app_config, plugins)
         self.event_handler: Optional[EventHandler] = None
@@ -548,28 +552,36 @@ async def stop_event(self, event_name: str):
             raise RuntimeError(f"Cannot stop non running event: {event_name}.")
 
     @staticmethod
-    def _config_effective_events(app_config: AppConfig) -> Dict[str, EventDescriptor]:
+    def _config_effective_events(app_config: AppConfig, enabled_groups: List[str]) -> Dict[str, EventDescriptor]:
         """
         Return effective events computed from user app config.
 
         Effective events could be result of splitting a single event in stages,
         using the "SHUFFLE" keyword, that will internaally generate 2 events.
-        Or for STREAMS that implementes the `__service__` method, both a STREAM
+        Or for STREAMS that implements the `__service__` method, both a STREAM
         and a SERVICE event will be generated.
+        Only events with groups defined in `enabled_groups` list will be returned.
+        If `enabled_groups` is empty, all events wil be considered.
         """
         effective_events: Dict[str, EventDescriptor] = {}
+        assert app_config.server
         for event_name, event_info in app_config.events.items():
-            impl = find_event_handler(app_config=app_config, event_name=event_name, event_info=event_info)
-            # Add events resultatn of splitting steps on SHUFFLE (stages)
-            splits = split_event_stages(app_config.app, event_name, event_info, impl)
-            effective_events.update(**splits)
-            # Add associated SERVICE events to streams
-            if event_info.type == EventType.STREAM and hasattr(impl, "__service__"):
-                effective_events[f"{event_name}$__service__"] = EventDescriptor(
-                    type=EventType.SERVICE,
-                    connections=event_info.connections,
-                    impl=event_info.impl,
-                )
+            if (
+                len(enabled_groups) == 0
+                or event_info.group == EventDescriptor.DEFAULT_GROUP
+                or event_info.group in enabled_groups
+            ):
+                impl = find_event_handler(app_config=app_config, event_name=event_name, event_info=event_info)
+                # Add events resultant of splitting steps on SHUFFLE (stages)
+                splits = split_event_stages(app_config.app, event_name, event_info, impl)
+                effective_events.update(**splits)
+                # Add associated SERVICE events to streams
+                if event_info.type == EventType.STREAM and hasattr(impl, "__service__"):
+                    effective_events[f"{event_name}$__service__"] = EventDescriptor(
+                        type=EventType.SERVICE,
+                        connections=event_info.connections,
+                        impl=event_info.impl,
+                    )
         return effective_events
 
     def _find_stream_datatype_handlers(self, event_name: str, event_info: EventDescriptor) -> Dict[str, type]:
@@ -624,18 +636,20 @@ async def stop(self):
             await app_engine.stop()
         logger.info(__name__, 'Engine stopped.')
 
-    async def start_app(self, app_config: AppConfig):
+    async def start_app(self, app_config: AppConfig, enabled_groups: List[str]):
         """
         Starts and register a Hopeit App into this engine instance
 
-        :param app_config: AppConfog, app configuration as specified in config module
+        :param app_config: AppConfig, app configuration as specified in config module
         """
         logger.info(__name__, f"Starting app={app_config.app_key()}...")
         plugins = [
             self.app_engine(app_key=plugin.app_key()).app_config
             for plugin in app_config.plugins
         ]
-        app_engine = await AppEngine(app_config=app_config, plugins=plugins).start()
+        app_engine = await AppEngine(app_config=app_config,
+                                     plugins=plugins,
+                                     enabled_groups=enabled_groups).start()
         self.app_engines[app_config.app_key()] = app_engine
         return app_engine
 
diff --git a/engine/src/hopeit/server/version.py b/engine/src/hopeit/server/version.py
index f88cd0a9..c81ec33e 100644
--- a/engine/src/hopeit/server/version.py
+++ b/engine/src/hopeit/server/version.py
@@ -8,7 +8,7 @@
 import sys
 
 ENGINE_NAME = "hopeit.engine"
-ENGINE_VERSION = "0.15.1"
+ENGINE_VERSION = "0.16.0"
 
 # Major.Minor version to be used in App versions and Api endpoints for Apps/Plugins
 APPS_API_VERSION = '.'.join(ENGINE_VERSION.split('.')[0:2])
diff --git a/engine/src/hopeit/server/web.py b/engine/src/hopeit/server/web.py
index c7901f83..0beb7132 100644
--- a/engine/src/hopeit/server/web.py
+++ b/engine/src/hopeit/server/web.py
@@ -70,13 +70,13 @@
 auth_info_default = {}
 
 
-def prepare_engine(*, config_files: List[str], api_file: Optional[str], start_streams: bool):
+def prepare_engine(*, config_files: List[str], api_file: Optional[str], enabled_groups: List[str], start_streams: bool):
     """
     Load configuration files and add hooks to setup engine server and apps,
     start streams and services.
     """
     logger.info("Loading engine config file=%s...", config_files[0])  # type: ignore
-    server_config = _load_engine_config(config_files[0])
+    server_config: ServerConfig = _load_engine_config(config_files[0])
 
     # Add startup hook to start engine
     web_server.on_startup.append(
@@ -100,7 +100,7 @@ def prepare_engine(*, config_files: List[str], api_file: Optional[str], start_st
     api.enable_swagger(server_config, web_server)
     for config in apps_config:
         web_server.on_startup.append(
-            partial(app_startup_hook, config)
+            partial(app_startup_hook, config, enabled_groups)
         )
 
     # Add hooks to start streams and service
@@ -140,14 +140,15 @@ async def stop_server():
     web_server = web.Application()
 
 
-async def app_startup_hook(config: AppConfig, *args, **kwargs):
+async def app_startup_hook(config: AppConfig, enabled_groups: List[str], *args, **kwargs):
     """
     Start Hopeit app specified by config
 
     :param config: AppConfig, configuration for the app to start
-    :param start_streams: if True all stream events in app will start consuming
+    :param enabled_groups: list of event groups names to enable. If empty,
+        all events will be enabled.
     """
-    app_engine = await runtime.server.start_app(app_config=config)
+    app_engine = await runtime.server.start_app(app_config=config, enabled_groups=enabled_groups)
     cors_origin = aiohttp_cors.setup(web_server, defaults={
         config.engine.cors_origin: aiohttp_cors.ResourceOptions(
             allow_credentials=True,
@@ -736,7 +737,8 @@ async def _handle_event_stop_invocation(
         return web.Response(status=500, body=str(e))
 
 
-ParsedArgs = namedtuple("ParsedArgs", ["host", "port", "path", "start_streams", "config_files", "api_file"])
+ParsedArgs = namedtuple("ParsedArgs", ["host", "port", "path", "start_streams",
+                                       "config_files", "api_file", "enabled_groups"])
 
 
 def parse_args(args) -> ParsedArgs:
@@ -748,6 +750,7 @@ def parse_args(args) -> ParsedArgs:
     --start-streams, optional True if to auto start all events of STREAM type
     --config-files, is a comma-separated list of hopeit apps config files relative or full paths
     --api-file, optional path to openapi.json file with at least openapi and info sections
+    --enabled-groups, optional list of group label to be started
     Example::
 
         python web.py --port=8020 --path=/tmp/hopeit.01 --config-files=test.json
@@ -764,10 +767,12 @@ def parse_args(args) -> ParsedArgs:
     parser.add_argument('--start-streams', action='store_true')
     parser.add_argument('--config-files')
     parser.add_argument('--api-file')
+    parser.add_argument('--enabled-groups')
 
     parsed_args = parser.parse_args(args=args)
     port = int(parsed_args.port) if parsed_args.port else 8020 if parsed_args.path is None else None
     config_files = parsed_args.config_files.split(',')
+    enabled_groups = parsed_args.enabled_groups.split(',') if parsed_args.enabled_groups else []
 
     return ParsedArgs(
         host=parsed_args.host,
@@ -775,7 +780,8 @@ def parse_args(args) -> ParsedArgs:
         path=parsed_args.path,
         start_streams=bool(parsed_args.start_streams),
         config_files=config_files,
-        api_file=parsed_args.api_file
+        api_file=parsed_args.api_file,
+        enabled_groups=enabled_groups
     )
 
 
@@ -784,6 +790,7 @@ def parse_args(args) -> ParsedArgs:
     prepare_engine(
         config_files=sys_args.config_files,
         api_file=sys_args.api_file,
-        start_streams=sys_args.start_streams
+        start_streams=sys_args.start_streams,
+        enabled_groups=sys_args.enabled_groups
     )
     serve(host=sys_args.host, path=sys_args.path, port=sys_args.port)
diff --git a/plugins/ops/apps-visualizer/api/openapi.json b/plugins/ops/apps-visualizer/api/openapi.json
index 6070830e..33156d96 100644
--- a/plugins/ops/apps-visualizer/api/openapi.json
+++ b/plugins/ops/apps-visualizer/api/openapi.json
@@ -1,12 +1,12 @@
 {
   "openapi": "3.0.3",
   "info": {
-    "version": "0.15",
+    "version": "0.16",
     "title": "Simple Example",
     "description": "Simple Example"
   },
   "paths": {
-    "/api/config-manager/0x15/runtime-apps-config": {
+    "/api/config-manager/0x16/runtime-apps-config": {
       "get": {
         "summary": "Config Manager: Runtime Apps Config",
         "description": "Returns the runtime config for the Apps running on this server",
@@ -62,11 +62,11 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
-    "/api/config-manager/0x15/cluster-apps-config": {
+    "/api/config-manager/0x16/cluster-apps-config": {
       "get": {
         "summary": "Config Manager: Cluster Apps Config",
         "description": "Handle remote access to runtime configuration for a group of hosts",
@@ -122,7 +122,7 @@
           }
         },
         "tags": [
-          "config_manager.0x15"
+          "config_manager.0x16"
         ]
       }
     },
@@ -209,11 +209,11 @@
           }
         },
         "tags": [
-          "apps_visualizer.0x15"
+          "apps_visualizer.0x16"
         ]
       }
     },
-    "/api/apps-visualizer/0x15/apps/events-graph": {
+    "/api/apps-visualizer/0x16/apps/events-graph": {
       "get": {
         "summary": "App Visualizer: Events Graph Data",
         "description": "App Visualizer: Events Graph Data",
@@ -287,11 +287,11 @@
           }
         },
         "tags": [
-          "apps_visualizer.0x15"
+          "apps_visualizer.0x16"
         ]
       }
     },
-    "/api/apps-visualizer/0x15/event-stats/live": {
+    "/api/apps-visualizer/0x16/event-stats/live": {
       "get": {
         "summary": "App Visualizer: Live Stats",
         "description": "App Visualizer: Live Stats",
@@ -365,7 +365,7 @@
           }
         },
         "tags": [
-          "apps_visualizer.0x15"
+          "apps_visualizer.0x16"
         ]
       }
     }
@@ -693,10 +693,14 @@
               "type": "string"
             },
             "default": []
+          },
+          "group": {
+            "type": "string",
+            "default": "DEFAULT"
           }
         },
         "x-module-name": "hopeit.app.config",
-        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    "
+        "description": "\n    Event Descriptor: configures event implementation\n\n    :field: type, EventType: type of event i.e.: GET, POST, MULTIPART, STREAM, SERVICE\n    :field: plug_mode, EventPlugMode: defines whether an event defined in a plugin is created in the\n        current app (ON_APP) or it will be created in the original plugin (STANDALONE, default)\n    :field: route, optional str: custom route for endpoint. If not specified route will be derived\n        from `/api/app_name/app_version/event_name`\n    :field: impl, optional str: custom event implementation Python module. If not specified, module\n        with same same as event will be imported.\n    :field: connections, list of EventConnection: specifies dependencies on other apps/endpoints,\n        that can be used by client plugins to call events on external apps\n    :field: read_stream, optional ReadStreamDescriptor: specifies source stream to read from.\n        Valid only for STREAM events.\n    :field: write_stream, optional WriteStreamDescriptor: for any type of events, resultant dataobjects will\n        be published to the specified stream.\n    :field: auth, list of AuthType: supported authentication schemas for this event. If not specified\n        application default will be used.\n    :field: setting_keys, list of str: by default EventContext will have access to the settings section\n        with the same name of the event using `settings = context.settings(datatype=MySettingsType)`.\n        In case additional sections are needed to be accessed from\n        EventContext, then a list of setting keys, including the name of the event if needed,\n        can be specified here. Then access to a `custom` key can be done using\n        `custom_settings = context.settings(key=\"customer\", datatype=MyCustomSettingsType)`\n    :field: dataobjects, list of str: list of full qualified dataobject types that this event can process.\n        When not specified, the engine will inspect the module implementation and find all datatypes supported\n        as payload in the functions defined as `__steps__`. In case of generic functions that support\n        `payload: DataObject` argument, then a list of full qualified datatypes must be specified here.\n    :field: group, str: group name, if none is assigned it is automatically assigned as 'DEFAULT'.\n    "
       },
       "EventConnection": {
         "type": "object",
@@ -823,7 +827,7 @@
           },
           "engine_version": {
             "type": "string",
-            "default": "0.15.1"
+            "default": "0.16.0"
           }
         },
         "x-module-name": "hopeit.server.config",
diff --git a/plugins/ops/apps-visualizer/src/hopeit/apps_visualizer/site/main.py b/plugins/ops/apps-visualizer/src/hopeit/apps_visualizer/site/main.py
index 9a4c776c..f7319706 100644
--- a/plugins/ops/apps-visualizer/src/hopeit/apps_visualizer/site/main.py
+++ b/plugins/ops/apps-visualizer/src/hopeit/apps_visualizer/site/main.py
@@ -70,7 +70,7 @@ async def __postprocess__(result: RuntimeAppsConfig, context: EventContext, resp
 
     app_prefix = f"{result.options.app_prefix}*" if result.options.app_prefix else 'All running apps'
     host_filter = f"*{result.options.host_filter}*" if result.options.host_filter else 'All servers'
-    view_type = "Expanded view" if result.options.expanded_view else "Standard view"
+    view_type = "Effective Events" if result.options.expanded_view else "Configured Events"
     live_type = "Live!" if result.options.live else "Static"
 
     refresh_endpoint_comps = (
diff --git a/pylintrc b/pylintrc
index 431b8d55..5390c48d 100644
--- a/pylintrc
+++ b/pylintrc
@@ -7,9 +7,6 @@
 # pygtk.require().
 #init-hook=
 
-# Profiled execution.
-profile=no
-
 # Add files or directories to the blacklist. They should be base names, not
 # paths.
 ignore=CVS
@@ -40,9 +37,6 @@ enable=indexing-exception,old-raise-syntax
 disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,not-context-manager,unused-argument
 
 
-# Set the cache size for astng objects.
-cache-size=500
-
 
 [REPORTS]
 
@@ -51,10 +45,6 @@ cache-size=500
 # mypackage.mymodule.MyReporterClass.
 output-format=text
 
-# Put messages in a separate file for each module / package specified on the
-# command line instead of printing them on stdout. Reports (if any) will be
-# written in a file name "pylint_global.[txt|html]".
-files-output=no
 
 # Tells whether to display a full report or only the messages
 reports=no
@@ -66,10 +56,6 @@ reports=no
 # (RP0004).
 evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
 
-# Add a comment according to your evaluation note. This is used by the global
-# evaluation report (RP0004).
-comment=no
-
 # Template used to display messages. This is a python new-style format string
 # used to format the message information. See doc for all details
 #msg-template=
@@ -85,10 +71,6 @@ ignore-mixin-members=yes
 # (useful for classes with attributes dynamically set).
 ignored-classes=SQLObject
 
-# When zope mode is activated, add a predefined set of Zope acquired attributes
-# to generated-members.
-zope=no
-
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E0201 when accessed. Python regular
 # expressions are accepted.
@@ -115,17 +97,6 @@ additional-builtins=
 
 [BASIC]
 
-# Required attributes for module, separated by a comma
-required-attributes=
-
-# List of builtins function names that should not be used, separated by a comma
-#good-functions=__init_event__,__postprocess__
-bad-functions=apply,input,reduce
-
-# Disable the report(s) with the given id(s).
-# All non-Google reports are disabled by default.
-disable-report=R0001,R0002,R0003,R0004,R0101,R0102,R0201,R0202,R0220,R0401,R0402,R0701,R0801,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R0921,R0922,R0923
-
 # Regular expression which should only match correct module names
 module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
 
@@ -179,25 +150,12 @@ docstring-min-length=10
 max-line-length=120
 
 # Regexp for a line that is allowed to be longer than the limit.
-ignore-long-lines=(?x)
-  (^\s*(import|from)\s
-   |\$Id:\s\/\/depot\/.+#\d+\s\$
-   |^[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*("[^"]\S+"|'[^']\S+')
-   |^\s*\#\ LINT\.ThenChange
-   |^[^#]*\#\ type:\ [a-zA-Z_][a-zA-Z0-9_.,[\] ]*$
-   |pylint
-   |"""
-   |\#
-   |lambda
-   |(https?|ftp):)
+ignore-long-lines=^\s*(# )?<?https?://\S+>?$
 
 # Allow the body of an if to be on the same line as the test if there is no
 # else.
 single-line-if-stmt=y
 
-# List of optional constructs for which whitespace checking is disabled
-no-space-check=
-
 # Maximum number of lines in a module
 max-module-lines=99999
 
@@ -247,10 +205,6 @@ int-import-graph=
 
 [CLASSES]
 
-# List of interface methods to ignore, separated by a comma. This is used for
-# instance to not check methods defines in Zope's Interface base class.
-ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
-
 # List of method names used to declare (i.e. assign) instance attributes.
 defining-attr-methods=__init__,__new__,setUp
 
@@ -302,33 +256,8 @@ max-public-methods=20
 overgeneral-exceptions=Exception,StandardError,BaseException
 
 
-[AST]
-
-# Maximum line length for lambdas
-short-func-length=1
-
-# List of module members that should be marked as deprecated.
-# All of the string functions are listed in 4.1.4 Deprecated string functions
-# in the Python 2.4 docs.
-deprecated-members=string.atof,string.atoi,string.atol,string.capitalize,string.expandtabs,string.find,string.rfind,string.index,string.rindex,string.count,string.lower,string.split,string.rsplit,string.splitfields,string.join,string.joinfields,string.lstrip,string.rstrip,string.strip,string.swapcase,string.translate,string.upper,string.ljust,string.rjust,string.center,string.zfill,string.replace,sys.exitfunc
-
-
-[DOCSTRING]
-
-# List of exceptions that do not need to be mentioned in the Raises section of
-# a docstring.
-ignore-exceptions=AssertionError,NotImplementedError,StopIteration,TypeError
-
-
-
 [TOKENS]
 
 # Number of spaces of indent required when the last token on the preceding line
 # is an open (, [, or {.
 indent-after-paren=4
-
-
-[HOPEIT LINES]
-
-# Regexp for a proper copyright notice.
-copyright=Copyright \d{4} Hopeit\. +All [Rr]ights [Rr]eserved\.


Test patch:
diff --git a/apps/benchmark/simple-benchmark/test/benchmark-docker.sh b/apps/benchmark/simple-benchmark/test/benchmark-docker.sh
index cd7d5602..52ab68f8 100755
--- a/apps/benchmark/simple-benchmark/test/benchmark-docker.sh
+++ b/apps/benchmark/simple-benchmark/test/benchmark-docker.sh
@@ -1,18 +1,18 @@
 #!/usr/bin/env bash
-echo ./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/give-me-something?item_id=string"
-./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/give-me-something?item_id=string"
+echo ./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/give-me-something?item_id=string"
+./wrk2/wrk -t8 -c40 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/give-me-something?item_id=string"
 sleep 5
-echo ./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-redis?item_id=string"
-./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-redis?item_id=string"
+echo ./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-redis?item_id=string"
+./wrk2/wrk -t6 -c32 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-redis?item_id=string"
 sleep 5
-echo ./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-fs?item_id=string"
-./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x15/query-something-fs?item_id=string"
+echo ./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-fs?item_id=string"
+./wrk2/wrk -t4 -c20 -d60s -R4000 --u_latency "http://localhost:8025/api/simple-benchmark/0x16/query-something-fs?item_id=string"
 sleep 5
-echo ./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/give-me-something?item_id=string"
-./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/give-me-something?item_id=string"
+echo ./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/give-me-something?item_id=string"
+./wrk2/wrk -t8 -c34 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/give-me-something?item_id=string"
 sleep 5
-echo ./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-redis?item_id=string"
-./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-redis?item_id=string"
+echo ./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-redis?item_id=string"
+./wrk2/wrk -t6 -c24 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-redis?item_id=string"
 sleep 5
-echo ./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-fs?item_id=string"
-./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x15/query-something-fs?item_id=string"
+echo ./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-fs?item_id=string"
+./wrk2/wrk -t4 -c14 -d60s -R4000 --u_latency "http://localhost:8021/api/simple-benchmark/0x16/query-something-fs?item_id=string"
diff --git a/apps/benchmark/simple-benchmark/test/warm-up-fs.sh b/apps/benchmark/simple-benchmark/test/warm-up-fs.sh
index 36634bd6..7d3aab5a 100755
--- a/apps/benchmark/simple-benchmark/test/warm-up-fs.sh
+++ b/apps/benchmark/simple-benchmark/test/warm-up-fs.sh
@@ -7,6 +7,6 @@ else
   host=localhost
 fi
 for i in {1..2000}; do
-  curl --silent --output /dev/null -d '{"id": "string", "user": "string"}' -H 'Content-Type: application/json' "http://$host:8021/api/simple-benchmark/0x15/save-something-fs"
+  curl --silent --output /dev/null -d '{"id": "string", "user": "string"}' -H 'Content-Type: application/json' "http://$host:8021/api/simple-benchmark/0x16/save-something-fs"
 done
 echo Done.
diff --git a/apps/examples/simple-example/test/integration/test_it_process_events.py b/apps/examples/simple-example/test/integration/test_it_process_events.py
index 481eafd0..b89eb914 100644
--- a/apps/examples/simple-example/test/integration/test_it_process_events.py
+++ b/apps/examples/simple-example/test/integration/test_it_process_events.py
@@ -1,4 +1,6 @@
-import pytest  # type: ignore
+import pytest
+
+from hopeit.server.version import APPS_ROUTE_VERSION  # type: ignore
 
 from hopeit.testing.apps import execute_event
 from hopeit.fs_storage.partition import get_partition_key
@@ -16,7 +18,7 @@ async def test_it_process_events(app_config,  # noqa: F811
     partition_key = get_partition_key(something_processed, partition_dateformat="%Y/%m/%d/%H")
     assert result == SomethingStored(
             path=(
-                f"{app_config.env['storage']['base_path']}simple_example.0x15.fs_storage.path/"
+                f"{app_config.env['storage']['base_path']}simple_example.{APPS_ROUTE_VERSION}.fs_storage.path/"
                 f"{partition_key}{result.payload.id}.json"
             ),
             payload=something_processed
diff --git a/engine/test/integration/server/test_it_web.py b/engine/test/integration/server/test_it_web.py
index fc7e86a4..c19301d8 100644
--- a/engine/test/integration/server/test_it_web.py
+++ b/engine/test/integration/server/test_it_web.py
@@ -3,7 +3,7 @@
 import uuid
 import asyncio
 import logging
-
+from typing import Optional, List
 import aiohttp
 import pytest
 from aiohttp import ClientResponse
@@ -519,13 +519,13 @@ async def call_stop_service(client):
     assert res.status == 200
 
 
-async def start_test_server(
-        mock_app_config, mock_plugin_config, streams=None):  # noqa: F811
+async def start_test_server(mock_app_config, mock_plugin_config,  # noqa: F811
+                            streams: bool, enabled_groups: List[str]):
     await server_startup_hook(mock_app_config.server)
-    await app_startup_hook(mock_plugin_config)
-    await app_startup_hook(mock_app_config)
+    await app_startup_hook(mock_plugin_config, enabled_groups)
+    await app_startup_hook(mock_app_config, enabled_groups)
     if streams:
-        await stream_startup_hook(mock_app_config)
+        await stream_startup_hook(mock_app_config, enabled_groups)
     print('Test engine started.', hopeit.server.web.web_server)
     await asyncio.sleep(5)
 
@@ -534,13 +534,16 @@ async def _setup(monkeypatch,
                  mock_app_config,  # noqa: F811
                  mock_plugin_config,  # noqa: F811
                  aiohttp_client,  # noqa: F811
-                 streams=None):
+                 streams=None,
+                 enabled_groups: Optional[List[str]] = None):
     stream_event = MockResult("ok: ok")
     monkeypatch.setattr(MockStreamManager, 'test_payload', stream_event)
     monkeypatch.setattr(MockEventHandler, 'test_track_ids', None)
 
     api.clear()
-    await start_test_server(mock_app_config, mock_plugin_config, streams)
+    if enabled_groups is None:
+        enabled_groups = []
+    await start_test_server(mock_app_config, mock_plugin_config, streams, enabled_groups)
     return await aiohttp_client(hopeit.server.web.web_server)
 
 
@@ -564,7 +567,7 @@ async def test_endpoints(monkeypatch,
                          mock_plugin_config,  # noqa: F811
                          aiohttp_client):  # noqa: F811
     test_client = await _setup(monkeypatch, mock_app_config, mock_plugin_config,
-                               aiohttp_client)
+                               aiohttp_client, [])
 
     logger = logging.getLogger()
     logger.setLevel(logging.DEBUG)
diff --git a/engine/test/mock_app/__init__.py b/engine/test/mock_app/__init__.py
index e23bd31d..92751639 100644
--- a/engine/test/mock_app/__init__.py
+++ b/engine/test/mock_app/__init__.py
@@ -111,18 +111,22 @@ def mock_app_config():
         events={
             "mock_event": EventDescriptor(
                 type=EventType.GET,
-                route='mock-app/test/mock-event-test'
+                route='mock-app/test/mock-event-test',
+                group='GROUP_A'
             ),
             "mock_event_logging": EventDescriptor(
-                type=EventType.GET
+                type=EventType.GET,
+                group='GROUP_A'
             ),
             "mock_post_event": EventDescriptor(
                 type=EventType.POST,
-                route='mock-app/test/mock-event-test'
+                route='mock-app/test/mock-event-test',
+                group='GROUP_A'
             ),
             "mock_multipart_event": EventDescriptor(
                 type=EventType.MULTIPART,
-                route='mock-app/test/mock-multipart-event-test'
+                route='mock-app/test/mock-multipart-event-test',
+                group='GROUP_B'
             ),
             "mock_post_nopayload": EventDescriptor(
                 type=EventType.POST,
@@ -142,14 +146,16 @@ def mock_app_config():
                     name='mock_stream',
                     consumer_group='mock_consumer_group'
                 ),
-                setting_keys=["custom_extra_settings"]
+                setting_keys=["custom_extra_settings"],
+                group='GROUP_A'
             ),
             "mock_stream_timeout": EventDescriptor(
                 type=EventType.STREAM,
                 read_stream=ReadStreamDescriptor(
                     name='mock_stream',
                     consumer_group='mock_consumer_group'
-                )
+                ),
+                group='GROUP_B'
             ),
             "mock_write_stream_event": EventDescriptor(
                 type=EventType.GET,
diff --git a/engine/test/mock_engine/__init__.py b/engine/test/mock_engine/__init__.py
index ea00a306..1b7bb74a 100644
--- a/engine/test/mock_engine/__init__.py
+++ b/engine/test/mock_engine/__init__.py
@@ -14,12 +14,13 @@
 
 
 class MockAppEngine(AppEngine):
-    def __init__(self, *, app_config: AppConfig, plugins: List[AppConfig]):
+    def __init__(self, *, app_config: AppConfig, plugins: List[AppConfig], enabled_groups: List[str]):
         """
         Creates an instance of the AppEngine
 
         :param app_config: AppConfig, Hopeit application configuration as specified in config module
         """
+        self.effective_events = self._config_effective_events(app_config, enabled_groups)
         self.app_config = app_config
         self.plugins = plugins
 
@@ -202,8 +203,8 @@ async def start(self):
     async def stop(self):
         pass
 
-    async def start_app(self, app_config: AppConfig):
-        self.app = MockAppEngine(app_config=app_config, plugins=[])
+    async def start_app(self, app_config: AppConfig, enabled_groups: List[str]):
+        self.app = MockAppEngine(app_config=app_config, plugins=[], enabled_groups=enabled_groups)
 
     def app_engine(self, *, app_key: str) -> AppEngine:
         return self.app
diff --git a/engine/test/unit/server/test_engine.py b/engine/test/unit/server/test_engine.py
index 06702dcf..28ce79f9 100644
--- a/engine/test/unit/server/test_engine.py
+++ b/engine/test/unit/server/test_engine.py
@@ -1,7 +1,7 @@
 import asyncio
 
 import pytest  # type: ignore
-from typing import Dict, Optional
+from typing import Dict, Optional, List
 
 from hopeit.app.context import EventContext, PostprocessHook
 from hopeit.server.config import AuthType
@@ -17,14 +17,19 @@
 from mock_plugin import mock_plugin_config  # type: ignore  # noqa: F401
 
 
-async def create_engine(app_config: AppConfig, plugin: AppConfig) -> AppEngine:
-    engine = await AppEngine(app_config=app_config, plugins=[plugin]).start()
+async def create_engine(app_config: AppConfig, plugin: AppConfig,
+                        enabled_groups: Optional[List[str]] = None) -> AppEngine:
+    if enabled_groups is None:
+        enabled_groups = []
+    engine = await AppEngine(app_config=app_config, plugins=[plugin], enabled_groups=enabled_groups).start()
     assert engine.stream_manager is not None
     return engine
 
 
-async def create_plugin_engine(plugin: AppConfig) -> AppEngine:
-    engine = await AppEngine(app_config=plugin, plugins=[]).start()
+async def create_plugin_engine(plugin: AppConfig, enabled_groups: Optional[List[str]] = None) -> AppEngine:
+    if enabled_groups is None:
+        enabled_groups = []
+    engine = await AppEngine(app_config=plugin, plugins=[], enabled_groups=enabled_groups).start()
     assert engine.stream_manager is None
     return engine
 
@@ -717,3 +722,77 @@ async def test_execute_collector(monkeypatch, mock_app_config, mock_plugin_confi
                             "track.session_id": "test_session_id"
                          })
     await engine.stop()
+
+
+@pytest.mark.asyncio
+async def test_start_all_groups(monkeypatch, mock_app_config, mock_plugin_config):  # noqa: F811
+    setup_mocks(monkeypatch)
+    engine = await create_engine(app_config=mock_app_config, plugin=mock_plugin_config, enabled_groups=None)
+    assert all(
+        event_name in engine.effective_events
+        for event_name in mock_app_config.events.keys()
+    )
+
+
+@pytest.mark.asyncio
+async def test_start_single_group(monkeypatch, mock_app_config, mock_plugin_config):  # noqa: F811
+    setup_mocks(monkeypatch)
+    engine = await create_engine(
+        app_config=mock_app_config,
+        plugin=mock_plugin_config,
+        enabled_groups=['GROUP_A']
+    )
+    assert len(engine.effective_events) == 25
+    assert all(
+        event_name in engine.effective_events
+        for event_name in ['mock_event', 'mock_post_event', 'mock_event_logging', 'mock_stream_event']
+    )
+    assert all(
+        event_name in engine.effective_events
+        for event_name, event_info in mock_app_config.events.items()
+        if event_info.group in ('DEFAULT', 'GROUP_A')
+    )
+
+
+@pytest.mark.asyncio
+async def test_start_multiple_groups(monkeypatch, mock_app_config, mock_plugin_config):  # noqa: F811
+    setup_mocks(monkeypatch)
+    engine = await create_engine(
+        app_config=mock_app_config,
+        plugin=mock_plugin_config,
+        enabled_groups=['GROUP_A', 'GROUP_B']
+    )
+    assert len(engine.effective_events) == 27
+    assert all(
+        event_name in engine.effective_events
+        for event_name in [
+            'mock_event', 'mock_post_event', 'mock_event_logging', 'mock_stream_event',
+            'mock_stream_timeout', 'mock_multipart_event'
+        ]
+    )
+    assert all(
+        event_name in engine.effective_events
+        for event_name, event_info in mock_app_config.events.items()
+        if event_info.group in ('DEFAULT', 'GROUP_A', 'GROUP_B')
+    )
+
+
+@pytest.mark.asyncio
+async def test_start_default_group(monkeypatch, mock_app_config, mock_plugin_config):  # noqa: F811
+    setup_mocks(monkeypatch)
+    engine = await create_engine(
+        app_config=mock_app_config,
+        plugin=mock_plugin_config,
+        enabled_groups=['DEFAULT']
+    )
+    # Checking count it should be 19 events + 2 split events == 21
+    assert len(engine.effective_events) == 21
+    assert all(
+        event_name not in engine.effective_events
+        for event_name in ['mock_event', 'mock_post_event', 'mock_event_logging', 'mock_stream_event']
+    )
+    assert all(
+        event_name in engine.effective_events
+        for event_name, event_info in mock_app_config.events.items()
+        if event_info.group == 'DEFAULT'
+    )
diff --git a/engine/test/unit/server/test_server.py b/engine/test/unit/server/test_server.py
index 68860733..5f794c15 100644
--- a/engine/test/unit/server/test_server.py
+++ b/engine/test/unit/server/test_server.py
@@ -1,8 +1,10 @@
 import pytest  # type: ignore
 
 from hopeit.app.config import AppConfig
+from hopeit.server import runtime
 from hopeit.server.engine import Server, AppEngine
 
+
 from mock_app import mock_app_config  # type: ignore  # noqa: F401
 from mock_plugin import mock_plugin_config  # type: ignore  # noqa: F401
 from mock_engine import MockAppEngine
@@ -11,9 +13,9 @@
 async def start_server(app_config: AppConfig,
                        plugin: AppConfig) -> Server:
     assert app_config.server
-    server = await Server().start(config=app_config.server)
-    await server.start_app(plugin)
-    await server.start_app(app_config)
+    server = await runtime.server.start(config=app_config.server)
+    await server.start_app(app_config=plugin, enabled_groups=[])
+    await server.start_app(app_config=app_config, enabled_groups=[])
     return server
 
 
diff --git a/engine/test/unit/server/test_web.py b/engine/test/unit/server/test_web.py
index 6156ad3b..f124d37f 100644
--- a/engine/test/unit/server/test_web.py
+++ b/engine/test/unit/server/test_web.py
@@ -13,43 +13,49 @@
 def test_port_path():
     args = ['--port=8080', '--path=/tmp/test', '--config-files=engine.json,test.json']
     result = parse_args(args)
-    assert result == (None, 8080, '/tmp/test', False, ['engine.json', 'test.json'], None)
+    assert result == (None, 8080, '/tmp/test', False, ['engine.json', 'test.json'], None, [])
 
 
 def test_stream_start():
     args = ['--start-streams', '--config-files=test.json']
     result = parse_args(args)
-    assert result == (None, 8020, None, True, ['test.json'], None)
+    assert result == (None, 8020, None, True, ['test.json'], None, [])
 
 
 def test_port_only():
     args = ['--port=8020', '--config-files=test.json']
     result = parse_args(args)
-    assert result == (None, 8020, None, False, ['test.json'], None)
+    assert result == (None, 8020, None, False, ['test.json'], None, [])
 
 
 def test_path_only():
     args = ['--path=/tmp/test', '--config-files=test.json']
     result = parse_args(args)
-    assert result == (None, None, '/tmp/test', False, ['test.json'], None)
+    assert result == (None, None, '/tmp/test', False, ['test.json'], None, [])
 
 
 def test_rest_config_only():
     args = ['--config-files=test.json']
     result = parse_args(args)
-    assert result == (None, 8020, None, False, ['test.json'], None)
+    assert result == (None, 8020, None, False, ['test.json'], None, [])
 
 
 def test_rest_config_with_host():
     args = ['--host=127.0.0.1', '--config-files=test.json']
     result = parse_args(args)
-    assert result == ('127.0.0.1', 8020, None, False, ['test.json'], None)
+    assert result == ('127.0.0.1', 8020, None, False, ['test.json'], None, [])
 
 
 def test_config_with_api_file():
     args = ['--config-files=test.json', '--api-file=openapi.json']
     result = parse_args(args)
-    assert result == (None, 8020, None, False, ['test.json'], 'openapi.json')
+    assert result == (None, 8020, None, False, ['test.json'], 'openapi.json', [])
+
+
+def test_config_with_groups():
+    args = ['--config-files=test.json', '--api-file=openapi.json', '--enabled-groups=g1,g2']
+    result = parse_args(args)
+    assert result == (None, 8020, None, False, ['test.json'], 'openapi.json', ['g1', 'g2'])
 
 
 class MockHooks:
@@ -86,7 +92,8 @@ def _serve():
         web.prepare_engine(
             config_files=['test_server_file.json', 'test_app_file.json', 'test_app_file2.json'],
             api_file='test_api_file.json',
-            start_streams=True
+            enabled_groups=[],
+            start_streams=True,
         )
         web.web_server.on_startup.append(_shutdown)
         web.serve(host='localhost', port=8020, path=None)
diff --git a/plugins/clients/apps-client/test/unit/__init__.py b/plugins/clients/apps-client/test/unit/__init__.py
index 80c80584..eed82f4b 100644
--- a/plugins/clients/apps-client/test/unit/__init__.py
+++ b/plugins/clients/apps-client/test/unit/__init__.py
@@ -171,7 +171,7 @@ async def init_mock_client_app(module, monkeypatch, mock_auth, app_config, event
             "authorization": "Bearer test-token"
         }
     ))
-    await engine.AppEngine(app_config=app_config, plugins=[], streams_enabled=False).start()
+    await engine.AppEngine(app_config=app_config, plugins=[], enabled_groups=[], streams_enabled=False).start()
 
 
 async def init_mock_client_app_plugin(module, monkeypatch, mock_auth, app_config, plugin_name, event_name, response):
@@ -190,4 +190,4 @@ async def init_mock_client_app_plugin(module, monkeypatch, mock_auth, app_config
         }
 
     ))
-    await engine.AppEngine(app_config=app_config, plugins=[], streams_enabled=False).start()
+    await engine.AppEngine(app_config=app_config, plugins=[], enabled_groups=[], streams_enabled=False).start()
diff --git a/plugins/ops/config-manager/test/integration/runtime_simple_example.json b/plugins/ops/config-manager/test/integration/runtime_simple_example.json
index d4a77c09..b2e09c81 100644
--- a/plugins/ops/config-manager/test/integration/runtime_simple_example.json
+++ b/plugins/ops/config-manager/test/integration/runtime_simple_example.json
@@ -80,7 +80,8 @@
             "setting_keys": [
               "fs_storage"
             ],
-            "dataobjects": []
+            "dataobjects": [],
+            "group": "g2"
           },
           "download_something": {
             "type": "GET",
@@ -106,7 +107,8 @@
             "connections": [],
             "auth": [],
             "setting_keys": [],
-            "dataobjects": []
+            "dataobjects": [],
+            "group": "g1"
           },
           "service.something_generator": {
             "type": "SERVICE",
@@ -833,7 +835,8 @@
           "setting_keys": [
             "fs_storage"
           ],
-          "dataobjects": []
+          "dataobjects": [],
+          "group": "g2"
         },
         "simple_example.${APPS_ROUTE_VERSION}.download_something": {
           "type": "GET",
@@ -859,7 +862,8 @@
           "connections": [],
           "auth": [],
           "setting_keys": [],
-          "dataobjects": []
+          "dataobjects": [],
+          "group": "g1"
         },
         "simple_example.${APPS_ROUTE_VERSION}.service.something_generator": {
           "type": "SERVICE",