Repository: allegro/marathon-consul
Pull request #134 (2016-10-13 14:58:04)

Release 1.0.0.


Patch:
diff --git a/.goxc.json b/.goxc.json
index 6f1460f..77dca39 100644
--- a/.goxc.json
+++ b/.goxc.json
@@ -2,7 +2,10 @@
 	"ArtifactsDest": "dist",
 	"TasksExclude": [
 		"go-vet",
-		"go-test"
+		"go-test",
+		"deb",
+		"deb-dev",
+		"deb-source"
 	],
 	"BuildConstraints": "linux,!arm darwin",
 	"PackageVersion": "0.4.2",
@@ -15,13 +18,6 @@
 			"repository": "deb",
 			"subject": "allegro"
 		},
-		"deb": {
-			"other-mapped-files": {
-				"/etc/init/marathon-consul.conf": "debian/marathon-consul.upstart",
-				"/etc/marathon-consul.d/config.json": "debian/config.json",
-				"/etc/systemd/system/marathon-consul.service": "debian/marathon-consul.service"
-			}
-		},
 		"publish-github": {
 			"body": "",
 			"owner": "allegro",
@@ -29,4 +25,4 @@
 		}
 	},
 	"ConfigVersion": "0.9"
-}
\ No newline at end of file
+}
diff --git a/.travis.yml b/.travis.yml
index 89fda95..27869ad 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,21 +2,24 @@ language: go
 
 go:
   - 1.7
-  - tip
 before_install:
   - go get golang.org/x/tools/cmd/cover
   - go get github.com/axw/gocov/gocov
   - go get github.com/modocache/gover
   - go get github.com/mattn/goveralls
   - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
+  - go get github.com/alecthomas/gometalinter
 script:
   - make deps
+  - gometalinter --install
+  - gometalinter ./... --deadline 120s --vendor -D dupl -D gotype -D errcheck -D gas -D golint -E gofmt
   - go test -coverprofile=apps.coverprofile ./apps
   - go test -coverprofile=config.coverprofile ./config
   - go test -coverprofile=consul.coverprofile ./consul
   - go test -coverprofile=events.coverprofile ./events
   - go test -coverprofile=marathon.coverprofile ./marathon
   - go test -coverprofile=metrics.coverprofile ./metrics
+  - go test -coverprofile=service.coverprofile ./service
   - go test -coverprofile=sync.coverprofile ./sync
   - go test -coverprofile=utils.coverprofile ./utils
   - go test -coverprofile=web.coverprofile ./web
diff --git a/Makefile b/Makefile
index 196bbcd..e7848c6 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,8 @@ TESTARGS?=-race
 CURRENT_DIR = $(shell pwd)
 SOURCEDIR = $(CURRENT_DIR)
 SOURCES := $(shell find $(SOURCEDIR) -name '*.go')
+VERSION=$(shell cat .goxc.json | python -c "import json,sys;obj=json.load(sys.stdin);print obj['PackageVersion'];")
+TEMPDIR := $(shell mktemp -d)
 
 all: deps build
 
@@ -24,10 +26,34 @@ test: deps $(SOURCES)
 	PATH=$(CURRENT_DIR)/bin:$(PATH) go test $(PACKAGES) $(TESTARGS)
 	go vet $(PACKAGES)
 
-release:
-	@rm -rf dist
+FPM-exists:
+	@fpm -v || \
+	(echo >&2 "FPM must be installed on the system. See https://github.com/jordansissel/fpm"; false)
+
+deb: FPM-exists build
+	mkdir -p dist/$(VERSION)/
+	cd dist/$(VERSION)/ && \
+	fpm -s dir \
+	    -t deb \
+        -n marathon-consul \
+        -v $(VERSION) \
+        --url="https://github.com/allegro/marathon-consul" \
+        --vendor=Allegro \
+        --maintainer="Allegro Group <opensource@allegro.pl>" \
+        --description "Marathon-consul service (performs Marathon Tasks registration as Consul Services for service discovery) Marathon-consul takes information provided by the Marathon event bus and forwards it to Consul agents. It also re-syncs all the information from Marathon to Consul on startup and repeats it with given interval." \
+        --deb-priority optional \
+        --workdir $(TEMPDIR) \
+        --license "Apache License, version 2.0" \
+        ../../bin/marathon-consul=/usr/bin/marathon-consul \
+        ../../debian/marathon-consul.service=/etc/systemd/system/marathon-consul.service \
+        ../../debian/marathon-consul.upstart=/etc/init/marathon-consul.conf \
+        ../../debian/config.json=/etc/marathon-consul.d/config.json
+
+release: deb
 	@go get github.com/laher/goxc
 	goxc
+
+bump:
 	goxc bump
 	git add .goxc.json
 	git commit -m "Bumped version"
diff --git a/README.md b/README.md
index 3398776..1169d49 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# marathon-consul [![Build Status](https://travis-ci.org/allegro/marathon-consul.svg?branch=master)](https://travis-ci.org/allegro/marathon-consul)&nbsp;[![Coverage Status](https://coveralls.io/repos/allegro/marathon-consul/badge.svg?branch=master&service=github)](https://coveralls.io/github/allegro/marathon-consul)&nbsp;[![Go Report Card](https://goreportcard.com/badge/github.com/allegro/marathon-consul)](https://goreportcard.com/report/github.com/allegro/marathon-consul)
+# marathon-consul [![Build Status](https://travis-ci.org/allegro/marathon-consul.svg?branch=master)](https://travis-ci.org/allegro/marathon-consul)[![Coverage Status](https://coveralls.io/repos/allegro/marathon-consul/badge.svg?branch=master&service=github)](https://coveralls.io/github/allegro/marathon-consul)
 
 
 Register [Marathon](https://mesosphere.github.io/marathon/) Tasks as [Consul](https://www.consul.io/) Services for service discovery.
@@ -145,13 +145,17 @@ curl -X GET http://localhost:8500/v1/catalog/service/my-new-app
 "ServiceTags": [
   "marathon",
   "varnish",
-  "metrics"
+  "metrics",
+  "marathon-task:my-new-app.6a95bb03-6ad3-11e6-beaf-080027a7aca0"
 ],
 
 ```
+- Every service registration contains an additional tag `marathon-task` specifying the Marathon task id related to this registration.
 - If there are multiple ports in use for the same app, note that only the first one will be registered by marathon-consul in Consul.
 
-### Task heathchecks
+If you need to register your task under multiple ports, refer to *Advanced usage* section below.
+
+### Task healthchecks
 
 - At least one HTTP healthcheck should be defined for a task. The task is registered when Marathon marks it as alive.
 - The provided HTTP healthcheck will be transferred to Consul.
@@ -174,6 +178,7 @@ config-file                 |                 | Path to a JSON file to read conf
 consul-auth                 | `false`         | Use Consul with authentication
 consul-auth-password        |                 | The basic authentication password
 consul-auth-username        |                 | The basic authentication username
+consul-ignored-healthchecks |                 | A comma separated blacklist of Marathon health check types that will not be migrated to Consul, e.g. command,tcp
 consul-name-separator       | `.`             | Separator used to create default service name for Consul
 consul-get-services-retry   | `3`             | Number of retries on failure when performing requests to Consul. Each retry uses different cached agent
 consul-max-agent-failures   | `3`             | Max number of consecutive request failures for agent before removal from cache
@@ -213,7 +218,81 @@ Endpoint  | Description
 `/health` | healthcheck - returns `OK`
 `/events` | event sink - returns `OK` if all keys are set in an event, error message otherwise
 
-### Known limitations
+## Advanced usage
+
+### Register under multiple ports
+
+If you need to map your Marathon task into multiple service registrations in Consul, you can configure marathon-consul 
+via Marathon's `portDefinitions`:
+
+```
+  "id": "my-new-app",
+  "labels": {
+    "consul": "",
+    "common-tag": "tag"
+  },
+  "portDefinitions": [
+    {
+      "labels": {
+        "consul": "my-app-custom-name"
+      }
+    },
+    {
+      "labels": {
+        "consul": "my-app-other-name",
+        "specific-tag": "tag"
+      }
+    }
+  ]
+```
+
+This configuration would result in two service registrations:
+
+```
+curl -X GET http://localhost:8500/v1/catalog/service/my-app-custom-name
+...
+"ServiceName": "my-app-custom-name",
+"ServiceTags": [
+  "marathon",
+  "common-tag",
+  "marathon-task:my-new-app.6a95bb03-6ad3-11e6-beaf-080027a7aca0"
+],
+"ServicePort": 31292,
+...
+
+curl -X GET http://localhost:8500/v1/catalog/service/my-app-other-name
+...
+"ServiceName": "my-app-other-name",
+"ServiceTags": [
+  "marathon",
+  "common-tag",
+  "specific-tag",
+  "marathon-task:my-new-app.6a95bb03-6ad3-11e6-beaf-080027a7aca0"
+],
+"ServicePort": 31293,
+...
+``` 
+
+If any port definition contains the `consul` label, then advanced configuration mode is enabled. As a result, only the ports 
+containing this label are registered, under the name specified as the label's value – with empty value resolved to default name.
+Names don't have to be unique – you can have multiple registrations under the same name, but on different ports, 
+perhaps with different tags. Note that the `consul` label still needs to be present in the top-level application labels, even
+though its value won't have any effect.
+
+Tags configured in the top-level application labels will be added to all registrations. Tags configured in the port definition 
+labels will be added only to corresponding registrations.
+
+All registrations share the same `marathon-task` tag.
+
+## Migration to version 1.x.x
+
+Until 1.x.x marathon-consul would register services in Consul with registration id equal to related Marathon task id. Since 1.x.x registration ids are different and
+an additional tag, `marathon-task`, is added to each registration.
+
+If you update marathon-consul from version 0.x.x to 1.x.x, expect the synchronization phase during the first startup to 
+reregister all healthy services managed by marathon-consul to the new format. Unhealthy services will get deregistered in the process.
+
+## Known limitations
 
 The following section describes known limitations in `marathon-consul`.
 
diff --git a/apps/app.go b/apps/app.go
index 26b0423..208e183 100644
--- a/apps/app.go
+++ b/apps/app.go
@@ -2,10 +2,13 @@ package apps
 
 import (
 	"encoding/json"
+	"strings"
+
+	log "github.com/Sirupsen/logrus"
 )
 
 // Only Marathon apps with this label will be registered in Consul
-const MARATHON_CONSUL_LABEL = "consul"
+const MarathonConsulLabel = "consul"
 
 type HealthCheck struct {
 	Path                   string `json:"path"`
@@ -21,37 +24,42 @@ type HealthCheck struct {
 	}
 }
 
-type AppWrapper struct {
+type PortDefinition struct {
+	Labels map[string]string `json:"labels"`
+}
+
+type appWrapper struct {
 	App App `json:"app"`
 }
 
-type AppsResponse struct {
+type Apps struct {
 	Apps []*App `json:"apps"`
 }
 
 type App struct {
-	Labels       map[string]string `json:"labels"`
-	HealthChecks []HealthCheck     `json:"healthChecks"`
-	ID           AppId             `json:"id"`
-	Tasks        []Task            `json:"tasks"`
+	Labels          map[string]string `json:"labels"`
+	HealthChecks    []HealthCheck     `json:"healthChecks"`
+	ID              AppID             `json:"id"`
+	Tasks           []Task            `json:"tasks"`
+	PortDefinitions []PortDefinition  `json:"portDefinitions"`
 }
 
 // Marathon Application Id (aka PathId)
 // Usually in the form of /rootGroup/subGroup/subSubGroup/name
 // allowed characters: lowercase letters, digits, hyphens, slash
-type AppId string
+type AppID string
 
-func (id AppId) String() string {
+func (id AppID) String() string {
 	return string(id)
 }
 
 func (app *App) IsConsulApp() bool {
-	_, ok := app.Labels[MARATHON_CONSUL_LABEL]
+	_, ok := app.Labels[MarathonConsulLabel]
 	return ok
 }
 
-func (app *App) ConsulName() string {
-	if value, ok := app.Labels[MARATHON_CONSUL_LABEL]; ok && !isSpecialConsulNameValue(value) {
+func (app *App) labelsToRawName(labels map[string]string) string {
+	if value, ok := labels[MarathonConsulLabel]; ok && !isSpecialConsulNameValue(value) {
 		return value
 	}
 	return app.ID.String()
@@ -62,15 +70,102 @@ func isSpecialConsulNameValue(name string) bool {
 }
 
 func ParseApps(jsonBlob []byte) ([]*App, error) {
-	apps := &AppsResponse{}
+	apps := &Apps{}
 	err := json.Unmarshal(jsonBlob, apps)
 
 	return apps.Apps, err
 }
 
 func ParseApp(jsonBlob []byte) (*App, error) {
-	wrapper := &AppWrapper{}
+	wrapper := &appWrapper{}
 	err := json.Unmarshal(jsonBlob, wrapper)
 
 	return &wrapper.App, err
 }
+
+type RegistrationIntent struct {
+	Name string
+	Port int
+	Tags []string
+}
+
+func (app *App) RegistrationIntentsNumber() int {
+	if !app.IsConsulApp() {
+		return 0
+	}
+
+	definitions := app.findConsulPortDefinitions()
+	if len(definitions) == 0 {
+		return 1
+	}
+
+	return len(definitions)
+}
+
+func (app *App) RegistrationIntents(task *Task, nameSeparator string) []*RegistrationIntent {
+	commonTags := labelsToTags(app.Labels)
+
+	definitions := app.findConsulPortDefinitions()
+	if len(definitions) == 0 {
+		return []*RegistrationIntent{
+			{
+				Name: app.labelsToName(app.Labels, nameSeparator),
+				Port: task.Ports[0],
+				Tags: commonTags,
+			},
+		}
+	}
+
+	var intents []*RegistrationIntent
+	for _, d := range definitions {
+		intents = append(intents, &RegistrationIntent{
+			Name: app.labelsToName(d.Labels, nameSeparator),
+			Port: task.Ports[d.Index],
+			Tags: append(commonTags, labelsToTags(d.Labels)...),
+		})
+	}
+	return intents
+}
+
+func marathonAppNameToServiceName(name string, nameSeparator string) string {
+	return strings.Replace(strings.Trim(strings.TrimSpace(name), "/"), "/", nameSeparator, -1)
+}
+
+func labelsToTags(labels map[string]string) []string {
+	tags := []string{}
+	for key, value := range labels {
+		if value == "tag" {
+			tags = append(tags, key)
+		}
+	}
+	return tags
+}
+
+func (app *App) labelsToName(labels map[string]string, nameSeparator string) string {
+	appConsulName := app.labelsToRawName(labels)
+	serviceName := marathonAppNameToServiceName(appConsulName, nameSeparator)
+	if serviceName == "" {
+		log.WithField("AppId", app.ID.String()).WithField("ConsulServiceName", appConsulName).
+			Warn("Warning! Invalid Consul service name provided for app. Will use default app name instead.")
+		return marathonAppNameToServiceName(app.ID.String(), nameSeparator)
+	}
+	return serviceName
+}
+
+type indexedPortDefinition struct {
+	Index  int
+	Labels map[string]string
+}
+
+func (app *App) findConsulPortDefinitions() []indexedPortDefinition {
+	var definitions []indexedPortDefinition
+	for i, d := range app.PortDefinitions {
+		if _, ok := d.Labels[MarathonConsulLabel]; ok {
+			definitions = append(definitions, indexedPortDefinition{
+				Index:  i,
+				Labels: d.Labels,
+			})
+		}
+	}
+	return definitions
+}
diff --git a/apps/task.go b/apps/task.go
index 164b665..6817575 100644
--- a/apps/task.go
+++ b/apps/task.go
@@ -6,25 +6,25 @@ import (
 )
 
 type Task struct {
-	ID                 TaskId              `json:"id"`
+	ID                 TaskID              `json:"id"`
 	TaskStatus         string              `json:"taskStatus"`
-	AppID              AppId               `json:"appId"`
+	AppID              AppID               `json:"appId"`
 	Host               string              `json:"host"`
 	Ports              []int               `json:"ports"`
 	HealthCheckResults []HealthCheckResult `json:"healthCheckResults"`
 }
 
-// Marathon Task Id
+// Marathon Task ID
 // Usually in the form of AppId.uuid with '/' replaced with '_'
-type TaskId string
+type TaskID string
 
-func (id TaskId) String() string {
+func (id TaskID) String() string {
 	return string(id)
 }
 
-func (id TaskId) AppId() AppId {
+func (id TaskID) AppID() AppID {
 	index := strings.LastIndex(id.String(), ".")
-	return AppId("/" + strings.Replace(id.String()[0:index], "_", "/", -1))
+	return AppID("/" + strings.Replace(id.String()[0:index], "_", "/", -1))
 }
 
 type HealthCheckResult struct {
diff --git a/config/config.go b/config/config.go
index c4dbde3..996004c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -17,7 +17,7 @@ import (
 )
 
 type Config struct {
-	Consul   consul.ConsulConfig
+	Consul   consul.Config
 	Web      web.Config
 	Sync     sync.Config
 	Marathon marathon.Config
@@ -72,6 +72,7 @@ func (config *Config) parseFlags() {
 	flag.Uint32Var(&config.Consul.AgentFailuresTolerance, "consul-max-agent-failures", 3, "Max number of consecutive request failures for agent before removal from cache")
 	flag.Uint32Var(&config.Consul.RequestRetries, "consul-get-services-retry", 3, "Number of retries on failure when performing requests to Consul. Each retry uses different cached agent")
 	flag.StringVar(&config.Consul.ConsulNameSeparator, "consul-name-separator", ".", "Separator used to create default service name for Consul")
+	flag.StringVar(&config.Consul.IgnoredHealthChecks, "consul-ignored-healthchecks", "", "A comma separated blacklist of Marathon health check types that will not be migrated to Consul, e.g. command,tcp")
 
 	// Web
 	flag.StringVar(&config.Web.Listen, "listen", ":4000", "Accept connections at this address")
diff --git a/consul/agent.go b/consul/agent.go
index dbc2ef1..91de414 100644
--- a/consul/agent.go
+++ b/consul/agent.go
@@ -9,10 +9,9 @@ import (
 )
 
 type Agent struct {
-	Client        *consulapi.Client
-	IP            string
-	failures      uint32
-	failureMetric string
+	Client   *consulapi.Client
+	IP       string
+	failures uint32
 }
 
 func (a *Agent) IncFailures() uint32 {
diff --git a/consul/agents.go b/consul/agents.go
index ccf7247..002f9ca 100644
--- a/consul/agents.go
+++ b/consul/agents.go
@@ -2,7 +2,7 @@ package consul
 
 import (
 	"crypto/tls"
-	"fmt"
+	"errors"
 	"math/rand"
 	"net/http"
 	"sync"
@@ -21,12 +21,12 @@ type Agents interface {
 
 type ConcurrentAgents struct {
 	agents map[string]*Agent
-	config *ConsulConfig
+	config *Config
 	lock   sync.Mutex
 	client *http.Client
 }
 
-func NewAgents(config *ConsulConfig) *ConcurrentAgents {
+func NewAgents(config *Config) *ConcurrentAgents {
 	client := &http.Client{
 		Transport: &http.Transport{
 			Proxy: http.ProxyFromEnvironment,
@@ -48,13 +48,13 @@ func (a *ConcurrentAgents) GetAnyAgent() (*Agent, error) {
 	defer a.lock.Unlock()
 
 	if len(a.agents) > 0 {
-		ipAddress := a.getRandomAgentIpAddress()
+		ipAddress := a.getRandomAgentIPAddress()
 		return a.agents[ipAddress], nil
 	}
-	return nil, fmt.Errorf("No Consul client available in agents cache")
+	return nil, errors.New("No Consul client available in agents cache")
 }
 
-func (a *ConcurrentAgents) getRandomAgentIpAddress() string {
+func (a *ConcurrentAgents) getRandomAgentIPAddress() string {
 	ipAddresses := []string{}
 	for ipAddress := range a.agents {
 		ipAddresses = append(ipAddresses, ipAddress)
diff --git a/consul/config.go b/consul/config.go
index 3c6388b..1d4cac1 100644
--- a/consul/config.go
+++ b/consul/config.go
@@ -2,7 +2,7 @@ package consul
 
 import "time"
 
-type ConsulConfig struct {
+type Config struct {
 	Auth                   Auth
 	Port                   string
 	SslEnabled             bool
@@ -15,6 +15,7 @@ type ConsulConfig struct {
 	RequestRetries         uint32
 	AgentFailuresTolerance uint32
 	ConsulNameSeparator    string
+	IgnoredHealthChecks    string
 }
 
 type Auth struct {
diff --git a/consul/consul.go b/consul/consul.go
index 3507f82..53539ad 100644
--- a/consul/consul.go
+++ b/consul/consul.go
@@ -1,6 +1,7 @@
 package consul
 
 import (
+	"errors"
 	"fmt"
 	"net/url"
 	"strings"
@@ -8,40 +9,32 @@ import (
 	log "github.com/Sirupsen/logrus"
 	"github.com/allegro/marathon-consul/apps"
 	"github.com/allegro/marathon-consul/metrics"
+	"github.com/allegro/marathon-consul/service"
 	"github.com/allegro/marathon-consul/utils"
 	consulapi "github.com/hashicorp/consul/api"
 )
 
-type ConsulServices interface {
-	GetAllServices() ([]*consulapi.CatalogService, error)
-	GetServices(name string) ([]*consulapi.CatalogService, error)
-	Register(task *apps.Task, app *apps.App) error
-	Deregister(serviceId apps.TaskId, agentAddress string) error
-	GetAgent(agentAddress string) (*consulapi.Client, error)
-	ServiceName(app *apps.App) string
-}
-
 type Consul struct {
 	agents Agents
-	config ConsulConfig
+	config Config
 }
 
-type ServicesProvider func(agent *consulapi.Client) ([]*consulapi.CatalogService, error)
+type ServicesProvider func(agent *consulapi.Client) ([]*service.Service, error)
 
-func New(config ConsulConfig) *Consul {
+func New(config Config) *Consul {
 	return &Consul{
 		agents: NewAgents(&config),
 		config: config,
 	}
 }
 
-func (c *Consul) GetServices(name string) ([]*consulapi.CatalogService, error) {
-	return c.getServicesUsingProviderWithRetriesOnAgentFailure(func(agent *consulapi.Client) ([]*consulapi.CatalogService, error) {
+func (c *Consul) GetServices(name string) ([]*service.Service, error) {
+	return c.getServicesUsingProviderWithRetriesOnAgentFailure(func(agent *consulapi.Client) ([]*service.Service, error) {
 		return c.getServicesUsingAgent(name, agent)
 	})
 }
 
-func (c *Consul) getServicesUsingProviderWithRetriesOnAgentFailure(provide ServicesProvider) ([]*consulapi.CatalogService, error) {
+func (c *Consul) getServicesUsingProviderWithRetriesOnAgentFailure(provide ServicesProvider) ([]*service.Service, error) {
 	for retry := uint32(0); retry <= c.config.RequestRetries; retry++ {
 		agent, err := c.agents.GetAnyAgent()
 		if err != nil {
@@ -60,61 +53,88 @@ func (c *Consul) getServicesUsingProviderWithRetriesOnAgentFailure(provide Servi
 			return services, nil
 		}
 	}
-	return nil, fmt.Errorf("An error occurred getting services from Consul. Giving up")
+	return nil, errors.New("An error occurred getting services from Consul. Giving up")
 }
 
-func (c *Consul) getServicesUsingAgent(name string, agent *consulapi.Client) ([]*consulapi.CatalogService, error) {
-	datacenters, err := agent.Catalog().Datacenters()
+func (c *Consul) getServicesUsingAgent(name string, agent *consulapi.Client) ([]*service.Service, error) {
+	dcAwareQueries, err := dcAwareQueriesForAllDCs(agent)
 	if err != nil {
 		return nil, err
 	}
-	var allServices []*consulapi.CatalogService
+	var allServices []*service.Service
 
-	for _, dc := range datacenters {
-		dcAwareQuery := &consulapi.QueryOptions{
-			Datacenter: dc,
-		}
-		services, _, err := agent.Catalog().Service(name, c.config.Tag, dcAwareQuery)
+	for _, dcAwareQuery := range dcAwareQueries {
+		allConsulServices, _, err := agent.Catalog().Service(name, c.config.Tag, dcAwareQuery)
 		if err != nil {
 			return nil, err
 		}
-		allServices = append(allServices, services...)
+		allServices = append(allServices, consulServicesToServices(allConsulServices)...)
 	}
 	return allServices, nil
 }
 
-func (c *Consul) GetAllServices() ([]*consulapi.CatalogService, error) {
-	return c.getServicesUsingProviderWithRetriesOnAgentFailure(c.getAllServices)
-}
-
-func (c *Consul) getAllServices(agent *consulapi.Client) ([]*consulapi.CatalogService, error) {
+func dcAwareQueriesForAllDCs(agent *consulapi.Client) ([]*consulapi.QueryOptions, error) {
 	datacenters, err := agent.Catalog().Datacenters()
 	if err != nil {
 		return nil, err
 	}
-	var allInstances []*consulapi.CatalogService
 
+	var queries []*consulapi.QueryOptions
 	for _, dc := range datacenters {
-		dcAwareQuery := &consulapi.QueryOptions{
+		queries = append(queries, &consulapi.QueryOptions{
 			Datacenter: dc,
-		}
-		services, _, err := agent.Catalog().Services(dcAwareQuery)
+		})
+	}
+
+	return queries, nil
+}
+
+func (c *Consul) GetAllServices() ([]*service.Service, error) {
+	return c.getServicesUsingProviderWithRetriesOnAgentFailure(c.getAllServices)
+}
+
+func (c *Consul) getAllServices(agent *consulapi.Client) ([]*service.Service, error) {
+	dcAwareQueries, err := dcAwareQueriesForAllDCs(agent)
+	if err != nil {
+		return nil, err
+	}
+	var allInstances []*service.Service
+
+	for _, dcAwareQuery := range dcAwareQueries {
+		consulServices, _, err := agent.Catalog().Services(dcAwareQuery)
 		if err != nil {
 			return nil, err
 		}
-		for service, tags := range services {
+		for consulService, tags := range consulServices {
 			if contains(tags, c.config.Tag) {
-				serviceInstances, _, err := agent.Catalog().Service(service, c.config.Tag, dcAwareQuery)
+				consulServiceInstances, _, err := agent.Catalog().Service(consulService, c.config.Tag, dcAwareQuery)
 				if err != nil {
 					return nil, err
 				}
-				allInstances = append(allInstances, serviceInstances...)
+				allInstances = append(allInstances, consulServicesToServices(consulServiceInstances)...)
 			}
 		}
 	}
 	return allInstances, nil
 }
 
+func consulServiceToService(consulService *consulapi.CatalogService) *service.Service {
+	return &service.Service{
+		ID:   service.ServiceId(consulService.ServiceID),
+		Name: consulService.ServiceName,
+		Tags: consulService.ServiceTags,
+		RegisteringAgentAddress: consulService.Address,
+	}
+}
+
+func consulServicesToServices(consulServices []*consulapi.CatalogService) []*service.Service {
+	var allServices []*service.Service
+	for _, c := range consulServices {
+		allServices = append(allServices, consulServiceToService(c))
+	}
+	return allServices
+}
+
 func contains(slice []string, search string) bool {
 	for _, element := range slice {
 		if element == search {
@@ -125,14 +145,14 @@ func contains(slice []string, search string) bool {
 }
 
 func (c *Consul) Register(task *apps.Task, app *apps.App) error {
-	service, err := c.marathonTaskToConsulService(task, app)
+	services, err := c.marathonTaskToConsulServices(task, app)
 	if err != nil {
 		return err
 	}
-	if value, ok := app.Labels[apps.MARATHON_CONSUL_LABEL]; ok && value == "true" {
+	if value, ok := app.Labels[apps.MarathonConsulLabel]; ok && value == "true" {
 		log.WithField("Id", app.ID).Warn("Warning! Application configuration is deprecated (labeled as `consul:true`). Support for special `true` value will be removed in the future!")
 	}
-	metrics.Time("consul.register", func() { err = c.register(service) })
+	metrics.Time("consul.register", func() { err = c.registerMultipleServices(services) })
 	if err != nil {
 		metrics.Mark("consul.register.error")
 	} else {
@@ -141,6 +161,18 @@ func (c *Consul) Register(task *apps.Task, app *apps.App) error {
 	return err
 }
 
+func (c *Consul) registerMultipleServices(services []*consulapi.AgentServiceRegistration) error {
+	var registerErrors []error
+	for _, s := range services {
+		registerErr := c.register(s)
+		if registerErr != nil {
+			registerErrors = append(registerErrors, registerErr)
+		}
+	}
+
+	return utils.MergeErrorsOrNil(registerErrors, fmt.Sprint("registering services"))
+}
+
 func (c *Consul) register(service *consulapi.AgentServiceRegistration) error {
 	agent, err := c.agents.GetAgent(service.Address)
 	if err != nil {
@@ -162,9 +194,59 @@ func (c *Consul) register(service *consulapi.AgentServiceRegistration) error {
 	return err
 }
 
-func (c *Consul) Deregister(serviceId apps.TaskId, agentAddress string) error {
+func (c *Consul) DeregisterByTask(taskID apps.TaskID) error {
+	services, err := c.findServicesByTaskID(taskID)
+	if err != nil {
+		return err
+	} else if len(services) == 0 {
+		return fmt.Errorf("Couldn't find any service matching task id %s", taskID)
+	}
+	return c.deregisterMultipleServices(services, taskID)
+}
+
+func (c *Consul) deregisterMultipleServices(services []*service.Service, taskID apps.TaskID) error {
+	var deregisterErrors []error
+	for _, s := range services {
+		deregisterErr := c.Deregister(s)
+		if deregisterErr != nil {
+			deregisterErrors = append(deregisterErrors, deregisterErr)
+		}
+	}
+
+	return utils.MergeErrorsOrNil(deregisterErrors, fmt.Sprintf("deregistering by task %s", taskID))
+}
+
+func (c *Consul) findServicesByTaskID(searchedTaskID apps.TaskID) ([]*service.Service, error) {
+	return c.getServicesUsingProviderWithRetriesOnAgentFailure(func(agent *consulapi.Client) ([]*service.Service, error) {
+		dcAwareQueries, err := dcAwareQueriesForAllDCs(agent)
+		if err != nil {
+			return nil, err
+		}
+
+		var allFound []*service.Service
+		searchedTag := service.MarathonTaskTag(searchedTaskID)
+		for _, dcAwareQuery := range dcAwareQueries {
+			consulServices, _, err := agent.Catalog().Services(dcAwareQuery)
+			if err != nil {
+				return nil, err
+			}
+			for consulService, tags := range consulServices {
+				if contains(tags, searchedTag) {
+					instancesForTask, _, err := agent.Catalog().Service(consulService, searchedTag, dcAwareQuery)
+					if err != nil {
+						return nil, err
+					}
+					allFound = append(allFound, consulServicesToServices(instancesForTask)...)
+				}
+			}
+		}
+		return allFound, nil
+	})
+}
+
+func (c *Consul) Deregister(toDeregister *service.Service) error {
 	var err error
-	metrics.Time("consul.deregister", func() { err = c.deregister(serviceId, agentAddress) })
+	metrics.Time("consul.deregister", func() { err = c.deregister(toDeregister) })
 	if err != nil {
 		metrics.Mark("consul.deregister.error")
 	} else {
@@ -173,78 +255,79 @@ func (c *Consul) Deregister(serviceId apps.TaskId, agentAddress string) error {
 	return err
 }
 
-func (c *Consul) deregister(serviceId apps.TaskId, agentAddress string) error {
-	agent, err := c.agents.GetAgent(agentAddress)
+func (c *Consul) deregister(toDeregister *service.Service) error {
+	agent, err := c.agents.GetAgent(toDeregister.RegisteringAgentAddress)
 	if err != nil {
 		return err
 	}
 
-	log.WithField("Id", serviceId).WithField("Address", agentAddress).Info("Deregistering")
+	log.WithField("Id", toDeregister.ID).WithField("Address", toDeregister.RegisteringAgentAddress).Info("Deregistering")
 
-	err = agent.Agent().ServiceDeregister(serviceId.String())
+	err = agent.Agent().ServiceDeregister(toDeregister.ID.String())
 	if err != nil {
-		log.WithError(err).WithField("Id", serviceId).WithField("Address", agentAddress).Error("Unable to deregister")
+		log.WithError(err).WithField("Id", toDeregister.ID).WithField("Address", toDeregister.RegisteringAgentAddress).Error("Unable to deregister")
 	}
 	return err
 }
 
-func (c *Consul) GetAgent(agentAddress string) (*consulapi.Client, error) {
-	return c.agents.GetAgent(agentAddress)
-}
-
-func (c *Consul) ServiceName(app *apps.App) string {
-	appConsulName := app.ConsulName()
-	serviceName := c.marathonAppNameToConsulServiceName(appConsulName)
-	if serviceName == "" {
-		log.WithField("AppId", app.ID.String()).WithField("ConsulServiceName", appConsulName).
-			Warn("Warning! Invalid Consul service name provided for app. Will use default app name instead.")
-		return c.marathonAppNameToConsulServiceName(app.ID.String())
-	}
-	return serviceName
-}
-
-func (c *Consul) marathonAppNameToConsulServiceName(name string) string {
-	return strings.Replace(strings.Trim(strings.TrimSpace(name), "/"), "/", c.config.ConsulNameSeparator, -1)
-}
-
-func (c *Consul) marathonTaskToConsulService(task *apps.Task, app *apps.App) (*consulapi.AgentServiceRegistration, error) {
+func (c *Consul) marathonTaskToConsulServices(task *apps.Task, app *apps.App) ([]*consulapi.AgentServiceRegistration, error) {
 	IP, err := utils.HostToIPv4(task.Host)
 	if err != nil {
 		return nil, err
 	}
 	serviceAddress := IP.String()
+	checks := c.marathonToConsulChecks(task, app.HealthChecks, serviceAddress)
+
+	var registrations []*consulapi.AgentServiceRegistration
+	for _, intent := range app.RegistrationIntents(task, c.config.ConsulNameSeparator) {
+		tags := append([]string{c.config.Tag}, intent.Tags...)
+		tags = append(tags, service.MarathonTaskTag(task.ID))
+		registrations = append(registrations, &consulapi.AgentServiceRegistration{
+			ID:      c.serviceID(task, intent.Name, intent.Port),
+			Name:    intent.Name,
+			Port:    intent.Port,
+			Address: serviceAddress,
+			Tags:    tags,
+			Checks:  checks,
+		})
+	}
+	return registrations, nil
+}
 
-	return &consulapi.AgentServiceRegistration{
-		ID:      task.ID.String(),
-		Name:    c.ServiceName(app),
-		Port:    task.Ports[0],
-		Address: serviceAddress,
-		Tags:    c.marathonLabelsToConsulTags(app.Labels),
-		Checks:  marathonToConsulChecks(task, app.HealthChecks, serviceAddress),
-	}, nil
+func (c *Consul) serviceID(task *apps.Task, name string, port int) string {
+	return fmt.Sprintf("%s_%s_%d", task.ID, name, port)
 }
 
-func marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, serviceAddress string) consulapi.AgentServiceChecks {
-	var checks consulapi.AgentServiceChecks = make(consulapi.AgentServiceChecks, 0, len(healthChecks))
+func (c *Consul) marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, serviceAddress string) consulapi.AgentServiceChecks {
+	var checks = make(consulapi.AgentServiceChecks, 0, len(healthChecks))
 
+	ignoredHealthCheckTypes := c.getIgnoredHealthCheckTypes()
 	for _, check := range healthChecks {
+		if contains(ignoredHealthCheckTypes, check.Protocol) {
+			log.WithField("Id", task.AppID.String()).WithField("Address", serviceAddress).
+				Info(fmt.Sprintf("Ignoring health check of type %s", check.Protocol))
+			continue
+		}
 		var port int
 		if check.Port != 0 {
 			port = check.Port
 		} else {
 			port = task.Ports[check.PortIndex]
 		}
+
+		consulCheck := consulapi.AgentServiceCheck{
+			Interval: fmt.Sprintf("%ds", check.IntervalSeconds),
+			Timeout:  fmt.Sprintf("%ds", check.TimeoutSeconds),
+			Status:   "passing",
+		}
+
 		switch check.Protocol {
 		case "HTTP", "HTTPS":
-			if parsedUrl, err := url.ParseRequestURI(check.Path); err == nil {
-				parsedUrl.Scheme = strings.ToLower(check.Protocol)
-				parsedUrl.Host = fmt.Sprintf("%s:%d", serviceAddress, port)
-
-				checks = append(checks, &consulapi.AgentServiceCheck{
-					HTTP:     parsedUrl.String(),
-					Interval: fmt.Sprintf("%ds", check.IntervalSeconds),
-					Timeout:  fmt.Sprintf("%ds", check.TimeoutSeconds),
-				})
+			if parsedURL, err := url.ParseRequestURI(check.Path); err == nil {
+				parsedURL.Scheme = strings.ToLower(check.Protocol)
+				parsedURL.Host = fmt.Sprintf("%s:%d", serviceAddress, port)
+				consulCheck.HTTP = parsedURL.String()
+				checks = append(checks, &consulCheck)
 			} else {
 				log.WithError(err).
 					WithField("Id", task.AppID.String()).
@@ -252,17 +335,11 @@ func marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, se
 					Warn(fmt.Sprintf("Could not parse provided path: %s", check.Path))
 			}
 		case "TCP":
-			checks = append(checks, &consulapi.AgentServiceCheck{
-				TCP:      fmt.Sprintf("%s:%d", serviceAddress, port),
-				Interval: fmt.Sprintf("%ds", check.IntervalSeconds),
-				Timeout:  fmt.Sprintf("%ds", check.TimeoutSeconds),
-			})
+			consulCheck.TCP = fmt.Sprintf("%s:%d", serviceAddress, port)
+			checks = append(checks, &consulCheck)
 		case "COMMAND":
-			checks = append(checks, &consulapi.AgentServiceCheck{
-				Script:   check.Command.Value,
-				Interval: fmt.Sprintf("%ds", check.IntervalSeconds),
-				Timeout:  fmt.Sprintf("%ds", check.TimeoutSeconds),
-			})
+			consulCheck.Script = check.Command.Value
+			checks = append(checks, &consulCheck)
 		default:
 			log.WithField("Id", task.AppID.String()).WithField("Address", serviceAddress).
 				Warn(fmt.Sprintf("Unrecognized check protocol %s", check.Protocol))
@@ -271,12 +348,32 @@ func marathonToConsulChecks(task *apps.Task, healthChecks []apps.HealthCheck, se
 	return checks
 }
 
-func (c *Consul) marathonLabelsToConsulTags(labels map[string]string) []string {
-	tags := []string{c.config.Tag}
-	for key, value := range labels {
-		if value == "tag" {
-			tags = append(tags, key)
+func (c *Consul) getIgnoredHealthCheckTypes() []string {
+	ignoredTypes := make([]string, 0)
+	for _, ignoredType := range strings.Split(strings.ToUpper(c.config.IgnoredHealthChecks), ",") {
+		var ignoredType = strings.TrimSpace(ignoredType)
+		if ignoredType != "" {
+			ignoredTypes = append(ignoredTypes, ignoredType)
 		}
 	}
-	return tags
+	return ignoredTypes
+}
+
+func (c *Consul) AddAgentsFromApps(apps []*apps.App) {
+	for _, app := range apps {
+		if !app.IsConsulApp() {
+			continue
+		}
+		for _, task := range app.Tasks {
+			err := c.AddAgent(task.Host)
+			if err != nil {
+				log.WithError(err).WithField("Node", task.Host).Error("Can't add agent node")
+			}
+		}
+	}
+}
+
+func (c *Consul) AddAgent(agentAddress string) error {
+	_, err := c.agents.GetAgent(agentAddress)
+	return err
 }
diff --git a/consul/consul_stub.go b/consul/consul_stub.go
index 557f2a1..681e0b1 100644
--- a/consul/consul_stub.go
+++ b/consul/consul_stub.go
@@ -1,113 +1,168 @@
 package consul
 
 import (
+	"fmt"
 	"sync"
 
 	"github.com/allegro/marathon-consul/apps"
+	"github.com/allegro/marathon-consul/service"
 	consulapi "github.com/hashicorp/consul/api"
 )
 
-type ConsulStub struct {
+// TODO this should be a service registry stub in the service package, requires abstracting from AgentServiceRegistration
+type Stub struct {
 	sync.RWMutex
-	services         map[apps.TaskId]*consulapi.AgentServiceRegistration
-	ErrorServices    map[apps.TaskId]error
-	ErrorGetServices map[string]error
-	consul           *Consul
+	services                   map[service.ServiceId]*consulapi.AgentServiceRegistration
+	failGetServicesForNames    map[string]bool
+	failRegisterForIDs         map[apps.TaskID]bool
+	failDeregisterByTaskForIDs map[apps.TaskID]bool
+	failDeregisterForIDs       map[service.ServiceId]bool
+	consul                     *Consul
 }
 
-func NewConsulStub() *ConsulStub {
+func NewConsulStub() *Stub {
 	return NewConsulStubWithTag("marathon")
 }
 
-func NewConsulStubWithTag(tag string) *ConsulStub {
-	return &ConsulStub{
-		services:         make(map[apps.TaskId]*consulapi.AgentServiceRegistration),
-		ErrorServices:    make(map[apps.TaskId]error),
-		ErrorGetServices: make(map[string]error),
-		consul:           New(ConsulConfig{Tag: tag, ConsulNameSeparator: "."}),
+func NewConsulStubWithTag(tag string) *Stub {
+	return &Stub{
+		services:                   make(map[service.ServiceId]*consulapi.AgentServiceRegistration),
+		failGetServicesForNames:    make(map[string]bool),
+		failRegisterForIDs:         make(map[apps.TaskID]bool),
+		failDeregisterByTaskForIDs: make(map[apps.TaskID]bool),
+		failDeregisterForIDs:       make(map[service.ServiceId]bool),
+		consul:                     New(Config{Tag: tag, ConsulNameSeparator: "."}),
 	}
 }
 
-func (c *ConsulStub) GetAllServices() ([]*consulapi.CatalogService, error) {
+func (c *Stub) GetAllServices() ([]*service.Service, error) {
 	c.RLock()
 	defer c.RUnlock()
-	var catalog []*consulapi.CatalogService
+	var allServices []*service.Service
 	for _, s := range c.services {
-		catalog = append(catalog, &consulapi.CatalogService{
-			Address:        s.Address,
-			ServiceAddress: s.Address,
-			ServicePort:    s.Port,
-			ServiceTags:    s.Tags,
-			ServiceID:      s.ID,
-			ServiceName:    s.Name,
+		allServices = append(allServices, &service.Service{
+			ID:   service.ServiceId(s.ID),
+			Name: s.Name,
+			Tags: s.Tags,
+			RegisteringAgentAddress: s.Address,
 		})
 	}
-	return catalog, nil
+	return allServices, nil
 }
 
-func (c *ConsulStub) GetServices(name string) ([]*consulapi.CatalogService, error) {
+func (c *Stub) FailGetServicesForName(failOnName string) {
+	c.failGetServicesForNames[failOnName] = true
+}
+
+func (c *Stub) FailRegisterForID(taskID apps.TaskID) {
+	c.failRegisterForIDs[taskID] = true
+}
+
+func (c *Stub) FailDeregisterByTaskForID(taskID apps.TaskID) {
+	c.failDeregisterByTaskForIDs[taskID] = true
+}
+
+func (c *Stub) FailDeregisterForID(serviceID service.ServiceId) {
+	c.failDeregisterForIDs[serviceID] = true
+}
+
+func (c *Stub) GetServices(name string) ([]*service.Service, error) {
 	c.RLock()
 	defer c.RUnlock()
-	if err, ok := c.ErrorGetServices[name]; ok {
-		return nil, err
+	if _, ok := c.failGetServicesForNames[name]; ok {
+		return nil, fmt.Errorf("Consul stub programmed to fail when getting services for name %s", name)
 	}
-	var catalog []*consulapi.CatalogService
+	var services []*service.Service
 	for _, s := range c.services {
 		if s.Name == name && contains(s.Tags, c.consul.config.Tag) {
-			catalog = append(catalog, &consulapi.CatalogService{
-				Address:        s.Address,
-				ServiceAddress: s.Address,
-				ServicePort:    s.Port,
-				ServiceTags:    s.Tags,
-				ServiceID:      s.ID,
-				ServiceName:    s.Name,
+			services = append(services, &service.Service{
+				ID:   service.ServiceId(s.ID),
+				Name: s.Name,
+				Tags: s.Tags,
+				RegisteringAgentAddress: s.Address,
 			})
 		}
 	}
-	return catalog, nil
+	return services, nil
 }
 
-func (c *ConsulStub) Register(task *apps.Task, app *apps.App) error {
+func (c *Stub) Register(task *apps.Task, app *apps.App) error {
 	c.Lock()
 	defer c.Unlock()
-	if err, ok := c.ErrorServices[task.ID]; ok {
+	if _, ok := c.failRegisterForIDs[task.ID]; ok {
+		return fmt.Errorf("Consul stub programmed to fail when registering task of id %s", task.ID.String())
+	}
+	serviceRegistrations, err := c.consul.marathonTaskToConsulServices(task, app)
+	if err != nil {
 		return err
-	} else {
-		service, err := c.consul.marathonTaskToConsulService(task, app)
-		if err != nil {
-			return err
+	}
+	for _, r := range serviceRegistrations {
+		c.services[service.ServiceId(r.ID)] = r
+	}
+	return nil
+}
+
+func (c *Stub) RegisterWithoutMarathonTaskTag(task *apps.Task, app *apps.App) {
+	c.Lock()
+	defer c.Unlock()
+	for _, intent := range app.RegistrationIntents(task, c.consul.config.ConsulNameSeparator) {
+		serviceRegistration := consulapi.AgentServiceRegistration{
+			ID:      task.ID.String(),
+			Name:    intent.Name,
+			Port:    intent.Port,
+			Address: task.Host,
+			Tags:    intent.Tags,
+			Checks:  consulapi.AgentServiceChecks{},
 		}
-		c.services[task.ID] = service
-		return nil
+		c.services[service.ServiceId(serviceRegistration.ID)] = &serviceRegistration
 	}
 }
 
-func (c *ConsulStub) ServiceName(app *apps.App) string {
-	return c.consul.ServiceName(app)
+func (c *Stub) RegisterOnlyFirstRegistrationIntent(task *apps.Task, app *apps.App) {
+	c.Lock()
+	defer c.Unlock()
+	serviceRegistrations, _ := c.consul.marathonTaskToConsulServices(task, app)
+	c.services[service.ServiceId(serviceRegistrations[0].ID)] = serviceRegistrations[0]
 }
 
-func (c *ConsulStub) Deregister(serviceId apps.TaskId, agent string) error {
+func (c *Stub) DeregisterByTask(taskID apps.TaskID) error {
 	c.Lock()
 	defer c.Unlock()
-	if err, ok := c.ErrorServices[serviceId]; ok {
-		return err
-	} else {
-		delete(c.services, serviceId)
-		return nil
+	if _, ok := c.failDeregisterByTaskForIDs[taskID]; ok {
+		return fmt.Errorf("Consul stub programmed to fail when deregistering task of id %s", taskID.String())
 	}
+	for _, x := range c.servicesMatchingTask(taskID) {
+		delete(c.services, service.ServiceId(x.ID))
+	}
+	return nil
 }
 
-func (c *ConsulStub) RegisteredServicesIds() []string {
-	c.RLock()
-	defer c.RUnlock()
-	services, _ := c.GetAllServices()
-	servicesIds := []string{}
-	for _, consulService := range services {
-		servicesIds = append(servicesIds, consulService.ServiceID)
+func (c *Stub) Deregister(toDeregister *service.Service) error {
+	c.Lock()
+	defer c.Unlock()
+	if _, ok := c.failDeregisterForIDs[toDeregister.ID]; ok {
+		return fmt.Errorf("Consul stub programmed to fail when deregistering service of id %s", toDeregister.ID)
 	}
-	return servicesIds
+	delete(c.services, toDeregister.ID)
+	return nil
 }
 
-func (c *ConsulStub) GetAgent(agentAddress string) (*consulapi.Client, error) {
-	return nil, nil
+func (c *Stub) servicesMatchingTask(taskID apps.TaskID) []*consulapi.AgentServiceRegistration {
+	matching := []*consulapi.AgentServiceRegistration{}
+	for _, s := range c.services {
+		if s.ID == taskID.String() || contains(s.Tags, fmt.Sprintf("marathon-task:%s", taskID.String())) {
+			matching = append(matching, s)
+		}
+	}
+	return matching
+}
+
+func (c *Stub) RegisteredTaskIDs() []apps.TaskID {
+	services, _ := c.GetAllServices()
+	taskIds := []apps.TaskID{}
+	for _, s := range services {
+		taskID, _ := s.TaskId()
+		taskIds = append(taskIds, taskID)
+	}
+	return taskIds
 }
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 874850b..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-marathon-consul () unreleased; urgency=low
-
-  * Initial import
-
- -- Allegro Group <opensource@allegro.pl>  Thu, 10 Dec 2015 09:40:43 +0100
\ No newline at end of file
diff --git a/debian/config.json b/debian/config.json
index de19080..04c2b18 100644
--- a/debian/config.json
+++ b/debian/config.json
@@ -15,7 +15,8 @@
     "Tag": "marathon",
     "Timeout": 3000000000,
     "AgentFailuresTolerance": 3,
-    "RequestRetries": 5
+    "RequestRetries": 5,
+    "IgnoredHealthChecks": ""
   },
   "Web": {
     "Listen": ":4000",
diff --git a/debian/control b/debian/control
deleted file mode 100644
index df8cfd2..0000000
--- a/debian/control
+++ /dev/null
@@ -1,17 +0,0 @@
-Source: marathon-consul
-Section: misc
-Priority: extra
-Maintainer: Allegro Group <opensource@allegro.pl>
-Build-Depends: debhelper (>= 9.1.0), golang-go
-Standards-Version: 3.9.4
-
-Package: marathon-consul
-Architecture: any
-Description: Marathon-consul service (performs Marathon Tasks registration as Consul Services for service discovery)
- Marathon-consul takes information provided by the Marathon event bus and forwards it to Consul agents. It also re-syncs all the information from Marathon to Consul on startup and repeats it with given interval.
-
-Package: marathon-consul-dev
-Architecture: all
-Description: Marathon-consul service (performs Marathon Tasks registration as Consul Services for service discovery) - development package
- Marathon-consul takes information provided by the Marathon event bus and forwards it to Consul agents. It also re-syncs all the information from Marathon to Consul on startup and repeats it with given interval.
-
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index ca404e0..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,3 +0,0 @@
-Files: *
-Copyright: 2015 Allegro Group
-License: Apache-2.0
diff --git a/debian/rules b/debian/rules
deleted file mode 100644
index b982a5d..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/make -f
-# -*- makefile -*-
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-export GOPATH=$(CURDIR)::/usr/share/gocode
-
-PKGDIR=debian/marathon-consul
-
-%:
-	dh $@
-
-clean:
-	dh_clean
-	rm -rf $(CURDIR)/bin/* $(CURDIR)/pkg/*
-	rm -f $(CURDIR)/goinstall.log
-
-binary-arch: clean
-	dh_prep
-	dh_installdirs
-	cd $(CURDIR)/src && go install ./...
-
-	mkdir -p $(PKGDIR)/usr/bin $(CURDIR)/bin/
-	mkdir -p $(PKGDIR)/usr/share/gopkg/ $(CURDIR)/pkg/
-
-	BINFILES=$(wildcard $(CURDIR)/bin/*)
-
-	for x in$(BINFILES); do \
-		cp $$x $(PKGDIR)/usr/bin/; \
-	done;
-
-	PKGFILES=$(wildcard $(CURDIR)/pkg/*.a)
-	for x in$(PKGFILES); do \
-		cp $$x $(PKGDIR)/usr/share/gopkg/; \
-	done;
-
-	dh_strip
-	dh_compress
-	dh_fixperms
-	dh_installdeb
-	dh_gencontrol
-	dh_md5sums
-	dh_builddeb
-
-binary: binary-arch
\ No newline at end of file
diff --git a/events/deployment_info.go b/events/deployment_info.go
deleted file mode 100644
index 4253735..0000000
--- a/events/deployment_info.go
+++ /dev/null
@@ -1,198 +0,0 @@
-package events
-
-import (
-	"encoding/json"
-	"strings"
-
-	"github.com/allegro/marathon-consul/apps"
-)
-
-type DeploymentEvent struct {
-	Type        string       `json:"eventType"`
-	Plan        *Plan        `json:"plan"`
-	CurrentStep *CurrentStep `json:"currentStep"`
-}
-
-type Plan struct {
-	Original *Deployments `json:"original"`
-	Target   *Deployments `json:"target"`
-}
-
-type Deployments struct {
-	Id     string         `json:"id"`
-	Apps   []*apps.App    `json:"apps"`
-	Groups []*Deployments `json:"groups"`
-}
-
-type CurrentStep struct {
-	Actions []*Action `json:"actions"`
-}
-
-type Action struct {
-	Type  string     `json:"type"`
-	AppId apps.AppId `json:"app"`
-}
-
-func (d *Deployments) groups() []*Deployments {
-	if d.Groups != nil {
-		return d.Groups
-	}
-	return []*Deployments{}
-}
-
-func (d *Deployments) apps() []*apps.App {
-	if d.Apps != nil {
-		return d.Apps
-	}
-	return []*apps.App{}
-}
-
-func (d *DeploymentEvent) originalDeployments() *Deployments {
-	if d.Plan != nil {
-		if d.Plan.Original != nil {
-			return d.Plan.Original
-		}
-	}
-	return &Deployments{}
-}
-
-func (d *DeploymentEvent) targetDeployments() *Deployments {
-	if d.Plan != nil {
-		if d.Plan.Target != nil {
-			return d.Plan.Target
-		}
-	}
-	return &Deployments{}
-}
-
-func (d *DeploymentEvent) actions() []*Action {
-	if d.CurrentStep != nil {
-		if d.CurrentStep.Actions != nil {
-			return d.CurrentStep.Actions
-		}
-	}
-	return []*Action{}
-}
-
-func (d *DeploymentEvent) StoppedConsulApps() []*apps.App {
-	return d.consulAppsWithActionPerformed(d.originalDeployments(), "StopApplication")
-}
-
-func (d *DeploymentEvent) RestartedConsulApps() []*apps.App {
-	return d.consulAppsWithActionPerformed(d.originalDeployments(), "RestartApplication")
-}
-
-func (d *DeploymentEvent) RenamedConsulApps() []*apps.App {
-	original := d.consulAppsWithActionPerformed(d.originalDeployments(), "RestartApplication")
-	renamedApps := []*apps.App{}
-	if len(original) > 0 {
-
-		target := d.consulAppsWithActionPerformed(d.targetDeployments(), "RestartApplication")
-		originalMap := d.appsMap(original)
-		targetMap := d.appsMap(target)
-		for id, originalApp := range originalMap {
-			targetApp, ok := targetMap[id]
-			if !ok || originalApp.ConsulName() != targetApp.ConsulName() {
-				renamedApps = append(renamedApps, originalApp)
-			}
-		}
-	}
-	return renamedApps
-}
-
-func (d *DeploymentEvent) appsMap(applications []*apps.App) map[apps.AppId]*apps.App {
-	result := make(map[apps.AppId]*apps.App)
-	for _, app := range applications {
-		result[app.ID] = app
-	}
-	return result
-}
-
-func (d *DeploymentEvent) consulAppsWithActionPerformed(deployments *Deployments, actionType string) []*apps.App {
-	appIds := make(map[apps.AppId]struct{})
-	var exists struct{}
-
-	for _, action := range d.actions() {
-		if action.Type == actionType {
-			appIds[action.AppId] = exists
-		}
-	}
-	return d.filterConsulApps(d.findAppsInDeploymentsGroup(appIds, deployments))
-}
-
-func (d *DeploymentEvent) filterConsulApps(allApps []*apps.App) []*apps.App {
-	filtered := []*apps.App{}
-	for _, app := range allApps {
-		if app.IsConsulApp() {
-			filtered = append(filtered, app)
-		}
-	}
-	return filtered
-}
-
-func (d *DeploymentEvent) findAppsInDeploymentsGroup(appIds map[apps.AppId]struct{}, deployment *Deployments) []*apps.App {
-	foundApps := []*apps.App{}
-	filteredAppIds := deployment.filterCurrentGroupAppIds(appIds)
-
-	foundInCurrentGroup := d.findAppsInCurrentDeploymentGroupApps(filteredAppIds, deployment)
-	for _, app := range foundInCurrentGroup {
-		foundApps = append(foundApps, app)
-	}
-
-	foundInChildGroups := d.findAppsInDeploymentChildGroups(filteredAppIds, deployment)
-	for _, app := range foundInChildGroups {
-		foundApps = append(foundApps, app)
-	}
-	return foundApps
-}
-
-func (d *DeploymentEvent) findAppsInCurrentDeploymentGroupApps(appIds map[apps.AppId]struct{}, deployment *Deployments) []*apps.App {
-	foundApps := []*apps.App{}
-	searchForCount := len(appIds)
-
-	for _, app := range deployment.apps() {
-		if searchForCount < 1 {
-			break
-		}
-		if _, ok := appIds[app.ID]; ok {
-			foundApps = append(foundApps, app)
-			searchForCount--
-		}
-	}
-	return foundApps
-}
-
-func (d *DeploymentEvent) findAppsInDeploymentChildGroups(appIds map[apps.AppId]struct{}, deployment *Deployments) []*apps.App {
-	foundApps := []*apps.App{}
-	searchForCount := len(appIds)
-
-	for _, group := range deployment.groups() {
-		if searchForCount < 1 {
-			break
-		}
-		foundInChildGroup := d.findAppsInDeploymentsGroup(appIds, group)
-		for _, app := range foundInChildGroup {
-			foundApps = append(foundApps, app)
-		}
-		searchForCount -= len(foundInChildGroup)
-	}
-	return foundApps
-}
-
-func (d *Deployments) filterCurrentGroupAppIds(appIds map[apps.AppId]struct{}) map[apps.AppId]struct{} {
-	filteredAppIds := make(map[apps.AppId]struct{})
-	var exists struct{}
-
-	for appId := range appIds {
-		if strings.HasPrefix(appId.String(), d.Id) {
-			filteredAppIds[appId] = exists
-		}
-	}
-	return filteredAppIds
-}
-
-func ParseDeploymentEvent(jsonBlob []byte) (*DeploymentEvent, error) {
-	deploymentInfo := &DeploymentEvent{}
-	err := json.Unmarshal(jsonBlob, deploymentInfo)
-	return deploymentInfo, err
-}
diff --git a/events/deployment_info.json b/events/deployment_info.json
deleted file mode 100644
index dff3dbe..0000000
--- a/events/deployment_info.json
+++ /dev/null
@@ -1,78 +0,0 @@
-{
-  "plan": {
-    "id": "25b0ad3d-28b3-4dfb-84a2-5ff19af1c467",
-    "original": {
-      "id": "/",
-      "apps": [{
-        "id": "/internalName",
-        "cmd": "python -m SimpleHTTPServer $PORT0",
-        "args": null,
-        "user": null,
-        "env": {},
-        "instances": 1,
-        "cpus": 0.1,
-        "mem": 16,
-        "disk": 0,
-        "executor": "",
-        "constraints": [],
-        "uris": [],
-        "storeUrls": [],
-        "ports": [10000],
-        "requirePorts": false,
-        "backoffSeconds": 1,
-        "backoffFactor": 1.15,
-        "maxLaunchDelaySeconds": 3600,
-        "container": null,
-        "healthChecks": [{
-          "path": "/",
-          "protocol": "HTTP",
-          "portIndex": 0,
-          "gracePeriodSeconds": 300,
-          "intervalSeconds": 60,
-          "timeoutSeconds": 20,
-          "maxConsecutiveFailures": 3,
-          "ignoreHttp1xx": false
-        }],
-        "dependencies": [],
-        "upgradeStrategy": {
-          "minimumHealthCapacity": 1,
-          "maximumOverCapacity": 1
-        },
-        "labels": {
-          "consul": "consulName"
-        },
-        "acceptedResourceRoles": null,
-        "version": "2016-01-18T12:21:04.494Z",
-        "versionInfo": {
-          "lastScalingAt": "2016-01-18T12:21:04.494Z",
-          "lastConfigChangeAt": "2016-01-18T12:21:04.494Z"
-        }
-      }],
-      "groups": [],
-      "dependencies": [],
-      "version": "2016-01-18T12:21:04.494Z"
-    },
-    "target": {
-      "id": "/",
-      "apps": [],
-      "groups": [],
-      "dependencies": [],
-      "version": "2016-01-18T12:22:07.141Z"
-    },
-    "steps": [{
-      "actions": [{
-        "type": "StopApplication",
-        "app": "/mirek"
-      }]
-    }],
-    "version": "2016-01-18T12:22:07.141Z"
-  },
-  "currentStep": {
-    "actions": [{
-      "type": "StopApplication",
-      "app": "/internalName"
-    }]
-  },
-  "eventType": "deployment_info",
-  "timestamp": "2016-01-18T12:22:07.170Z"
-}
\ No newline at end of file
diff --git a/events/deployment_step_success.json b/events/deployment_step_success.json
deleted file mode 100644
index 637559d..0000000
--- a/events/deployment_step_success.json
+++ /dev/null
@@ -1,238 +0,0 @@
-{
-  "plan": {
-    "id": "5f9e565a-d2f7-418e-bccf-161810de5672",
-    "original": {
-      "id": "/",
-      "apps": [
-        {
-          "id": "/other",
-          "cmd": "python -m SimpleHTTPServer $PORT0",
-          "args": null,
-          "user": null,
-          "env": {},
-          "instances": 2,
-          "cpus": 0.1,
-          "mem": 16,
-          "disk": 0,
-          "executor": "",
-          "constraints": [],
-          "uris": [],
-          "storeUrls": [],
-          "ports": [
-            10000
-          ],
-          "requirePorts": false,
-          "backoffSeconds": 1,
-          "backoffFactor": 1.15,
-          "maxLaunchDelaySeconds": 3600,
-          "container": null,
-          "healthChecks": [
-            {
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }
-          ],
-          "dependencies": [],
-          "upgradeStrategy": {
-            "minimumHealthCapacity": 1,
-            "maximumOverCapacity": 1
-          },
-          "labels": {
-            "consul": "true"
-          },
-          "acceptedResourceRoles": null,
-          "version": "2016-01-19T14:07:00.013Z",
-          "versionInfo": {
-            "lastScalingAt": "2016-01-19T14:07:00.013Z",
-            "lastConfigChangeAt": "2016-01-19T14:07:00.013Z"
-          }
-        },
-        {
-          "id": "/a",
-          "cmd": "python -m SimpleHTTPServer $PORT0",
-          "args": null,
-          "user": null,
-          "env": {},
-          "instances": 1,
-          "cpus": 0.1,
-          "mem": 16,
-          "disk": 0,
-          "executor": "",
-          "constraints": [],
-          "uris": [],
-          "storeUrls": [],
-          "ports": [
-            10001
-          ],
-          "requirePorts": false,
-          "backoffSeconds": 1,
-          "backoffFactor": 1.15,
-          "maxLaunchDelaySeconds": 3600,
-          "container": null,
-          "healthChecks": [
-            {
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }
-          ],
-          "dependencies": [],
-          "upgradeStrategy": {
-            "minimumHealthCapacity": 1,
-            "maximumOverCapacity": 1
-          },
-          "labels": {
-            "consul": "b"
-          },
-          "acceptedResourceRoles": null,
-          "version": "2016-01-19T14:32:43.064Z",
-          "versionInfo": {
-            "lastScalingAt": "2016-01-19T14:32:43.064Z",
-            "lastConfigChangeAt": "2016-01-19T14:32:43.064Z"
-          }
-        }
-      ],
-      "groups": [],
-      "dependencies": [],
-      "version": "2016-01-19T14:32:43.064Z"
-    },
-    "target": {
-      "id": "/",
-      "apps": [
-        {
-          "id": "/other",
-          "cmd": "python -m SimpleHTTPServer $PORT0",
-          "args": null,
-          "user": null,
-          "env": {},
-          "instances": 2,
-          "cpus": 0.1,
-          "mem": 16,
-          "disk": 0,
-          "executor": "",
-          "constraints": [],
-          "uris": [],
-          "storeUrls": [],
-          "ports": [
-            10000
-          ],
-          "requirePorts": false,
-          "backoffSeconds": 1,
-          "backoffFactor": 1.15,
-          "maxLaunchDelaySeconds": 3600,
-          "container": null,
-          "healthChecks": [
-            {
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }
-          ],
-          "dependencies": [],
-          "upgradeStrategy": {
-            "minimumHealthCapacity": 1,
-            "maximumOverCapacity": 1
-          },
-          "labels": {
-            "consul": "true"
-          },
-          "acceptedResourceRoles": null,
-          "version": "2016-01-19T14:07:00.013Z",
-          "versionInfo": {
-            "lastScalingAt": "2016-01-19T14:07:00.013Z",
-            "lastConfigChangeAt": "2016-01-19T14:07:00.013Z"
-          }
-        },
-        {
-          "id": "/a",
-          "cmd": "python -m SimpleHTTPServer $PORT0",
-          "args": null,
-          "user": null,
-          "env": {},
-          "instances": 1,
-          "cpus": 0.1,
-          "mem": 16,
-          "disk": 0,
-          "executor": "",
-          "constraints": [],
-          "uris": [],
-          "storeUrls": [],
-          "ports": [
-            10001
-          ],
-          "requirePorts": false,
-          "backoffSeconds": 1,
-          "backoffFactor": 1.15,
-          "maxLaunchDelaySeconds": 3600,
-          "container": null,
-          "healthChecks": [
-            {
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }
-          ],
-          "dependencies": [],
-          "upgradeStrategy": {
-            "minimumHealthCapacity": 1,
-            "maximumOverCapacity": 1
-          },
-          "labels": {
-            "consul": "c"
-          },
-          "acceptedResourceRoles": null,
-          "version": "2016-01-20T10:12:08.248Z",
-          "versionInfo": {
-            "lastScalingAt": "2016-01-20T10:12:08.248Z",
-            "lastConfigChangeAt": "2016-01-20T10:12:08.248Z"
-          }
-        }
-      ],
-      "groups": [],
-      "dependencies": [],
-      "version": "2016-01-20T10:12:08.248Z"
-    },
-    "steps": [
-      {
-        "actions": [
-          {
-            "type": "RestartApplication",
-            "app": "/a"
-          }
-        ]
-      }
-    ],
-    "version": "2016-01-20T10:12:08.248Z"
-  },
-  "currentStep": {
-    "actions": [
-      {
-        "type": "RestartApplication",
-        "app": "/a"
-      }
-    ]
-  },
-  "eventType": "deployment_step_success",
-  "timestamp": "2016-01-20T10:13:08.412Z"
-}
diff --git a/events/deployment_step_success_with_groups.json b/events/deployment_step_success_with_groups.json
deleted file mode 100644
index f7fd631..0000000
--- a/events/deployment_step_success_with_groups.json
+++ /dev/null
@@ -1,148 +0,0 @@
-{
-  "plan": {
-    "id": "eaeaade6-f485-4271-9058-913a885efa27",
-    "original": {
-      "id": "/",
-      "apps": [],
-      "groups": [{
-        "id": "/com.example",
-        "apps": [],
-        "groups": [{
-          "id": "/com.example/things",
-          "apps": [{
-            "id": "/com.example/things/something",
-            "cmd": "python -m SimpleHTTPServer $PORT0",
-            "args": null,
-            "user": null,
-            "env": {},
-            "instances": 1,
-            "cpus": 0.1,
-            "mem": 16,
-            "disk": 0,
-            "executor": "",
-            "constraints": [],
-            "uris": [],
-            "storeUrls": [],
-            "ports": [10000],
-            "requirePorts": false,
-            "backoffSeconds": 1,
-            "backoffFactor": 1.15,
-            "maxLaunchDelaySeconds": 3600,
-            "container": null,
-            "healthChecks": [{
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }],
-            "dependencies": [],
-            "upgradeStrategy": {
-              "minimumHealthCapacity": 1,
-              "maximumOverCapacity": 1
-            },
-            "labels": {
-              "consul": "something"
-            },
-            "acceptedResourceRoles": null,
-            "ipAddress": null,
-            "version": "2016-01-27T08:36:36.016Z",
-            "versionInfo": {
-              "lastScalingAt": "2016-01-27T08:36:36.016Z",
-              "lastConfigChangeAt": "2016-01-27T08:36:36.016Z"
-            }
-          }],
-          "groups": [],
-          "dependencies": [],
-          "version": "2016-01-27T08:36:36.016Z"
-        }],
-        "dependencies": [],
-        "version": "2016-01-27T08:36:36.016Z"
-      }],
-      "dependencies": [],
-      "version": "2016-01-27T08:36:36.016Z"
-    },
-    "target": {
-      "id": "/",
-      "apps": [],
-      "groups": [{
-        "id": "/com.example",
-        "apps": [],
-        "groups": [{
-          "id": "/com.example/things",
-          "apps": [{
-            "id": "/com.example/things/something",
-            "cmd": "python -m SimpleHTTPServer $PORT0",
-            "args": null,
-            "user": null,
-            "env": {},
-            "instances": 1,
-            "cpus": 0.1,
-            "mem": 16,
-            "disk": 0,
-            "executor": "",
-            "constraints": [],
-            "uris": [],
-            "storeUrls": [],
-            "ports": [10000],
-            "requirePorts": false,
-            "backoffSeconds": 1,
-            "backoffFactor": 1.15,
-            "maxLaunchDelaySeconds": 3600,
-            "container": null,
-            "healthChecks": [{
-              "path": "/",
-              "protocol": "HTTP",
-              "portIndex": 0,
-              "gracePeriodSeconds": 300,
-              "intervalSeconds": 60,
-              "timeoutSeconds": 20,
-              "maxConsecutiveFailures": 3,
-              "ignoreHttp1xx": false
-            }],
-            "dependencies": [],
-            "upgradeStrategy": {
-              "minimumHealthCapacity": 1,
-              "maximumOverCapacity": 1
-            },
-            "labels": {
-              "consul": "somethingDifferent"
-            },
-            "acceptedResourceRoles": null,
-            "ipAddress": null,
-            "version": "2016-01-27T08:38:43.039Z",
-            "versionInfo": {
-              "lastScalingAt": "2016-01-27T08:38:43.039Z",
-              "lastConfigChangeAt": "2016-01-27T08:38:43.039Z"
-            }
-          }],
-          "groups": [],
-          "dependencies": [],
-          "version": "2016-01-27T08:38:43.039Z"
-        }],
-        "dependencies": [],
-        "version": "2016-01-27T08:38:43.039Z"
-      }],
-      "dependencies": [],
-      "version": "2016-01-27T08:38:43.039Z"
-    },
-    "steps": [{
-      "actions": [{
-        "type": "RestartApplication",
-        "app": "/com.example/things/something"
-      }]
-    }],
-    "version": "2016-01-27T08:38:43.039Z"
-  },
-  "currentStep": {
-    "actions": [{
-      "type": "RestartApplication",
-      "app": "/com.example/things/something"
-    }]
-  },
-  "eventType": "deployment_step_success",
-  "timestamp": "2016-01-27T08:39:43.235Z"
-}
\ No newline at end of file
diff --git a/events/events.go b/events/events.go
index 30f40fe..3b23cea 100644
--- a/events/events.go
+++ b/events/events.go
@@ -7,26 +7,22 @@ import (
 	"github.com/allegro/marathon-consul/apps"
 )
 
-var (
-	ErrNoEvent = errors.New("no event")
-)
-
 type Event interface {
 	Apps() []*apps.App
 	GetType() string
 }
 
-type BaseEvent struct {
+type baseEvent struct {
 	Type string `json:"eventType"`
 }
 
 func EventType(jsonBlob []byte) (string, error) {
-	event := BaseEvent{}
+	event := baseEvent{}
 	err := json.Unmarshal(jsonBlob, &event)
 	if err != nil {
 		return "", err
 	} else if event.Type == "" {
-		return "", ErrNoEvent
+		return "", errors.New("no event")
 	} else {
 		return event.Type, nil
 	}
diff --git a/events/task_health_change.go b/events/task_health_change.go
index 514e6c7..f085e8b 100644
--- a/events/task_health_change.go
+++ b/events/task_health_change.go
@@ -8,9 +8,9 @@ import (
 
 type TaskHealthChange struct {
 	Timestamp  string      `json:"timestamp"`
-	ID         apps.TaskId `json:"id"`
+	ID         apps.TaskID `json:"id"`
 	TaskStatus string      `json:"taskStatus"`
-	AppID      apps.AppId  `json:"appId"`
+	AppID      apps.AppID  `json:"appId"`
 	Version    string      `json:"version"`
 	Alive      bool        `json:"alive"`
 }
diff --git a/events/unhealthy_task_kill.go b/events/unhealthy_task_kill.go
index ac4093b..771bde1 100644
--- a/events/unhealthy_task_kill.go
+++ b/events/unhealthy_task_kill.go
@@ -8,8 +8,8 @@ import (
 
 type UnhealthyTaskKilled struct {
 	Timestamp string      `json:"timestamp"`
-	ID        apps.TaskId `json:"taskId"`
-	AppID     apps.AppId  `json:"appId"`
+	ID        apps.TaskID `json:"taskId"`
+	AppID     apps.AppID  `json:"appId"`
 	Version   string      `json:"version"`
 }
 
diff --git a/install_consul.sh b/install_consul.sh
index fef61c8..88aecae 100755
--- a/install_consul.sh
+++ b/install_consul.sh
@@ -20,7 +20,7 @@ hash consul 2>/dev/null || {
         os="windows"
         arch="386"
     fi
-    version="0.6.4"
+    version="0.7.0"
     archive="consul_${version}_${os}_${arch}.zip"
 
     mkdir -p bin
diff --git a/main.go b/main.go
index dfa9efb..405f43e 100644
--- a/main.go
+++ b/main.go
@@ -5,7 +5,7 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/allegro/marathon-consul/config"
-	service "github.com/allegro/marathon-consul/consul"
+	"github.com/allegro/marathon-consul/consul"
 	"github.com/allegro/marathon-consul/marathon"
 	"github.com/allegro/marathon-consul/metrics"
 	"github.com/allegro/marathon-consul/sync"
@@ -28,16 +28,16 @@ func main() {
 		log.Fatal(err.Error())
 	}
 
-	service := service.New(config.Consul)
+	consulInstance := consul.New(config.Consul)
 	remote, err := marathon.New(config.Marathon)
 	if err != nil {
 		log.Fatal(err.Error())
 	}
 
-	sync := sync.New(config.Sync, remote, service)
+	sync := sync.New(config.Sync, remote, consulInstance, consulInstance.AddAgentsFromApps)
 	go sync.StartSyncServicesJob()
 
-	handler, stop := web.NewHandler(config.Web, remote, service)
+	handler, stop := web.NewHandler(config.Web, remote, consulInstance)
 	defer stop()
 
 	// set up routes
diff --git a/marathon/marathon.go b/marathon/marathon.go
index 0f50e27..0c78418 100644
--- a/marathon/marathon.go
+++ b/marathon/marathon.go
@@ -16,8 +16,8 @@ import (
 
 type Marathoner interface {
 	ConsulApps() ([]*apps.App, error)
-	App(apps.AppId) (*apps.App, error)
-	Tasks(apps.AppId) ([]*apps.Task, error)
+	App(apps.AppID) (*apps.App, error)
+	Tasks(apps.AppID) ([]*apps.Task, error)
 	Leader() (string, error)
 }
 
@@ -56,10 +56,10 @@ func New(config Config) (*Marathon, error) {
 	}, nil
 }
 
-func (m Marathon) App(appId apps.AppId) (*apps.App, error) {
-	log.WithField("Location", m.Location).Debug("Asking Marathon for " + appId)
+func (m Marathon) App(appID apps.AppID) (*apps.App, error) {
+	log.WithField("Location", m.Location).Debug("Asking Marathon for " + appID)
 
-	body, err := m.get(m.urlWithQuery(fmt.Sprintf("/v2/apps/%s", appId), params{"embed": "apps.tasks"}))
+	body, err := m.get(m.urlWithQuery(fmt.Sprintf("/v2/apps/%s", appID), params{"embed": "apps.tasks"}))
 	if err != nil {
 		return nil, err
 	}
@@ -69,7 +69,7 @@ func (m Marathon) App(appId apps.AppId) (*apps.App, error) {
 
 func (m Marathon) ConsulApps() ([]*apps.App, error) {
 	log.WithField("Location", m.Location).Debug("Asking Marathon for apps")
-	body, err := m.get(m.urlWithQuery("/v2/apps", params{"embed": "apps.tasks", "label": apps.MARATHON_CONSUL_LABEL}))
+	body, err := m.get(m.urlWithQuery("/v2/apps", params{"embed": "apps.tasks", "label": apps.MarathonConsulLabel}))
 	if err != nil {
 		return nil, err
 	}
@@ -77,14 +77,14 @@ func (m Marathon) ConsulApps() ([]*apps.App, error) {
 	return apps.ParseApps(body)
 }
 
-func (m Marathon) Tasks(app apps.AppId) ([]*apps.Task, error) {
+func (m Marathon) Tasks(app apps.AppID) ([]*apps.Task, error) {
 	log.WithFields(log.Fields{
 		"Location": m.Location,
 		"Id":       app,
 	}).Debug("asking Marathon for tasks")
 
-	trimmedAppId := strings.Trim(app.String(), "/")
-	body, err := m.get(m.url(fmt.Sprintf("/v2/apps/%s/tasks", trimmedAppId)))
+	trimmedAppID := strings.Trim(app.String(), "/")
+	body, err := m.get(m.url(fmt.Sprintf("/v2/apps/%s/tasks", trimmedAppID)))
 	if err != nil {
 		return nil, err
 	}
@@ -140,7 +140,7 @@ func (m Marathon) get(url string) ([]byte, error) {
 }
 
 func (m Marathon) logHTTPError(resp *http.Response, err error) {
-	var statusCode string = "???"
+	statusCode := "???"
 	if resp != nil {
 		statusCode = fmt.Sprintf("%d", resp.StatusCode)
 	}
diff --git a/marathon/marathon_stub.go b/marathon/marathon_stub.go
index b623f9e..7eb1e28 100644
--- a/marathon/marathon_stub.go
+++ b/marathon/marathon_stub.go
@@ -1,15 +1,15 @@
 package marathon
 
 import (
-	"fmt"
+	"errors"
 
 	"github.com/allegro/marathon-consul/apps"
 )
 
 type MarathonerStub struct {
 	AppsStub     []*apps.App
-	AppStub      map[apps.AppId]*apps.App
-	TasksStub    map[apps.AppId][]*apps.Task
+	AppStub      map[apps.AppID]*apps.App
+	TasksStub    map[apps.AppID][]*apps.Task
 	leader       string
 	interactions bool
 }
@@ -19,22 +19,20 @@ func (m *MarathonerStub) ConsulApps() ([]*apps.App, error) {
 	return m.AppsStub, nil
 }
 
-func (m *MarathonerStub) App(id apps.AppId) (*apps.App, error) {
+func (m *MarathonerStub) App(id apps.AppID) (*apps.App, error) {
 	m.interactions = true
 	if app, ok := m.AppStub[id]; ok {
 		return app, nil
-	} else {
-		return nil, fmt.Errorf("app not found")
 	}
+	return nil, errors.New("app not found")
 }
 
-func (m *MarathonerStub) Tasks(appId apps.AppId) ([]*apps.Task, error) {
+func (m *MarathonerStub) Tasks(appID apps.AppID) ([]*apps.Task, error) {
 	m.interactions = true
-	if app, ok := m.TasksStub[appId]; ok {
+	if app, ok := m.TasksStub[appID]; ok {
 		return app, nil
-	} else {
-		return nil, fmt.Errorf("app not found")
 	}
+	return nil, errors.New("app not found")
 }
 
 func (m *MarathonerStub) Leader() (string, error) {
@@ -53,8 +51,8 @@ func MarathonerStubWithLeaderForApps(leader string, args ...*apps.App) *Marathon
 }
 
 func MarathonerStubForApps(args ...*apps.App) *MarathonerStub {
-	appsMap := make(map[apps.AppId]*apps.App)
-	tasksMap := make(map[apps.AppId][]*apps.Task)
+	appsMap := make(map[apps.AppID]*apps.App)
+	tasksMap := make(map[apps.AppID][]*apps.Task)
 
 	for _, app := range args {
 		appsMap[app.ID] = app
diff --git a/service/service.go b/service/service.go
new file mode 100644
index 0000000..6a4c2a3
--- /dev/null
+++ b/service/service.go
@@ -0,0 +1,43 @@
+package service
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/allegro/marathon-consul/apps"
+)
+
+type ServiceId string
+
+func (id ServiceId) String() string {
+	return string(id)
+}
+
+type Service struct {
+	ID                      ServiceId
+	Name                    string
+	Tags                    []string
+	RegisteringAgentAddress string
+}
+
+func (s *Service) TaskId() (apps.TaskID, error) {
+	for _, tag := range s.Tags {
+		if strings.HasPrefix(tag, "marathon-task:") {
+			return apps.TaskID(strings.TrimPrefix(tag, "marathon-task:")), nil
+		}
+	}
+	return apps.TaskID(""), errors.New("marathon-task tag missing")
+}
+
+func MarathonTaskTag(taskId apps.TaskID) string {
+	return fmt.Sprintf("marathon-task:%s", taskId)
+}
+
+type ServiceRegistry interface {
+	GetAllServices() ([]*Service, error)
+	GetServices(name string) ([]*Service, error)
+	Register(task *apps.Task, app *apps.App) error
+	DeregisterByTask(taskId apps.TaskID) error
+	Deregister(toDeregister *Service) error
+}
diff --git a/sync/config.go b/sync/config.go
index c240d97..57ea4db 100644
--- a/sync/config.go
+++ b/sync/config.go
@@ -4,7 +4,7 @@ import "time"
 
 type Config struct {
 	Enabled  bool
+	Force    bool
 	Interval time.Duration
 	Leader   string
-	Force    bool
 }
diff --git a/sync/sync.go b/sync/sync.go
index a65f609..394f25b 100644
--- a/sync/sync.go
+++ b/sync/sync.go
@@ -7,20 +7,22 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/allegro/marathon-consul/apps"
-	service "github.com/allegro/marathon-consul/consul"
 	"github.com/allegro/marathon-consul/marathon"
 	"github.com/allegro/marathon-consul/metrics"
-	consul "github.com/hashicorp/consul/api"
+	"github.com/allegro/marathon-consul/service"
 )
 
 type Sync struct {
-	config   Config
-	marathon marathon.Marathoner
-	service  service.ConsulServices
+	config              Config
+	marathon            marathon.Marathoner
+	serviceRegistry     service.ServiceRegistry
+	syncStartedListener startedListener
 }
 
-func New(config Config, marathon marathon.Marathoner, service service.ConsulServices) *Sync {
-	return &Sync{config, marathon, service}
+type startedListener func(apps []*apps.App)
+
+func New(config Config, marathon marathon.Marathoner, serviceRegistry service.ServiceRegistry, syncStartedListener startedListener) *Sync {
+	return &Sync{config, marathon, serviceRegistry, syncStartedListener}
 }
 
 func (s *Sync) StartSyncServicesJob() *time.Ticker {
@@ -37,14 +39,13 @@ func (s *Sync) StartSyncServicesJob() *time.Ticker {
 
 	ticker := time.NewTicker(s.config.Interval)
 	go func() {
-		s.SyncServices()
+		if err := s.SyncServices(); err != nil {
+			log.WithError(err).Error("An error occured while performing sync")
+		}
 		for {
-			select {
-			case <-ticker.C:
-				err := s.SyncServices()
-				if err != nil {
-					log.WithError(err).Error("An error occured while performing sync")
-				}
+			<-ticker.C
+			if err := s.SyncServices(); err != nil {
+				log.WithError(err).Error("An error occured while performing sync")
 			}
 		}
 	}()
@@ -68,15 +69,15 @@ func (s *Sync) syncServices() error {
 		return fmt.Errorf("Can't get Marathon apps: %v", err)
 	}
 
-	s.addAgentNodes(apps)
+	s.syncStartedListener(apps)
 
-	services, err := s.service.GetAllServices()
+	services, err := s.serviceRegistry.GetAllServices()
 	if err != nil {
 		return fmt.Errorf("Can't get Consul services: %v", err)
 	}
 
-	s.deregisterConsulServicesNotFoundInMarathon(apps, services)
 	s.registerAppTasksNotFoundInConsul(apps, services)
+	s.deregisterConsulServicesNotFoundInMarathon(apps, services)
 
 	log.Info("Syncing services finished")
 	return nil
@@ -114,60 +115,56 @@ func (s *Sync) resolveHostname() error {
 	return nil
 }
 
-func (s *Sync) addAgentNodes(apps []*apps.App) {
-	nodes := make(map[string]struct{})
-	var exists struct{}
-	for _, app := range apps {
-		if !app.IsConsulApp() {
-			continue
-		}
-		for _, task := range app.Tasks {
-			nodes[task.Host] = exists
-		}
-	}
-	for node := range nodes {
-		_, err := s.service.GetAgent(node)
-		if err != nil {
-			log.WithError(err).WithField("Node", node).Error("Can't add agent node")
+func (s *Sync) deregisterConsulServicesNotFoundInMarathon(marathonApps []*apps.App, services []*service.Service) {
+	runningTasks := s.marathonTaskIdsSet(marathonApps)
+	for _, service := range services {
+		taskIDInTag, err := service.TaskId()
+		taskIDNotFoundInTag := err != nil
+		if taskIDNotFoundInTag {
+			log.WithField("Id", service.ID).WithError(err).
+				Warn("Couldn't extract marathon task id, deregistering since sync should have reregistered it already")
 		}
-	}
-}
 
-func (s *Sync) deregisterConsulServicesNotFoundInMarathon(marathonApps []*apps.App, consulServices []*consul.CatalogService) {
-	marathonTaskIdsSet := s.marathonTaskIdsSet(marathonApps)
-	for _, service := range consulServices {
-		serviceId := apps.TaskId(service.ServiceID)
-		if _, ok := marathonTaskIdsSet[serviceId]; !ok {
-			err := s.service.Deregister(serviceId, service.Address)
+		if _, isRunning := runningTasks[taskIDInTag]; !isRunning || taskIDNotFoundInTag {
+			err := s.serviceRegistry.Deregister(service)
 			if err != nil {
 				log.WithError(err).WithFields(log.Fields{
-					"Id":      service.ServiceID,
-					"Address": service.Address,
+					"Id":      service.ID,
+					"Address": service.RegisteringAgentAddress,
 				}).Error("Can't deregister service")
 			}
 		} else {
-			log.WithField("Id", service.ServiceID).Debug("Service is running")
+			log.WithField("Id", service.ID).Debug("Service is running")
 		}
 	}
 }
 
-func (s *Sync) registerAppTasksNotFoundInConsul(marathonApps []*apps.App, consulServices []*consul.CatalogService) {
-	consulServicesIdsSet := s.consulServiceIdsSet(consulServices)
+func (s *Sync) registerAppTasksNotFoundInConsul(marathonApps []*apps.App, services []*service.Service) {
+	registrationsUnderTaskIds := s.taskIdsInConsulServices(services)
 	for _, app := range marathonApps {
 		if !app.IsConsulApp() {
 			log.WithField("Id", app.ID).Debug("Not a Consul app, skipping registration")
 			continue
 		}
+		expectedRegistrations := app.RegistrationIntentsNumber()
 		for _, task := range app.Tasks {
-			if _, ok := consulServicesIdsSet[task.ID]; !ok {
+			registrations := registrationsUnderTaskIds[task.ID]
+			if registrations < expectedRegistrations {
+				if registrations != 0 {
+					log.WithField("Id", task.ID).WithField("HasRegistrations", registrations).
+						WithField("ExpectedRegistrations", expectedRegistrations).Info("Registering missing service registrations")
+				}
 				if task.IsHealthy() {
-					err := s.service.Register(&task, app)
+					err := s.serviceRegistry.Register(&task, app)
 					if err != nil {
 						log.WithError(err).WithField("Id", task.ID).Error("Can't register task")
 					}
 				} else {
 					log.WithField("Id", task.ID).Debug("Task is not healthy. Not Registering")
 				}
+			} else if registrations > expectedRegistrations {
+				log.WithField("Id", task.ID).WithField("HasRegistrations", registrations).
+					WithField("ExpectedRegistrations", expectedRegistrations).Warn("Skipping task with excess registrations")
 			} else {
 				log.WithField("Id", task.ID).Debug("Task already registered in Consul")
 			}
@@ -175,17 +172,18 @@ func (s *Sync) registerAppTasksNotFoundInConsul(marathonApps []*apps.App, consul
 	}
 }
 
-func (s *Sync) consulServiceIdsSet(services []*consul.CatalogService) map[apps.TaskId]struct{} {
-	servicesSet := make(map[apps.TaskId]struct{})
-	var exists struct{}
+func (s *Sync) taskIdsInConsulServices(services []*service.Service) map[apps.TaskID]int {
+	serviceCounters := make(map[apps.TaskID]int)
 	for _, service := range services {
-		servicesSet[apps.TaskId(service.ServiceID)] = exists
+		if taskID, err := service.TaskId(); err == nil {
+			serviceCounters[taskID]++
+		}
 	}
-	return servicesSet
+	return serviceCounters
 }
 
-func (s *Sync) marathonTaskIdsSet(marathonApps []*apps.App) map[apps.TaskId]struct{} {
-	tasksSet := make(map[apps.TaskId]struct{})
+func (s *Sync) marathonTaskIdsSet(marathonApps []*apps.App) map[apps.TaskID]struct{} {
+	tasksSet := make(map[apps.TaskID]struct{})
 	var exists struct{}
 	for _, app := range marathonApps {
 		for _, task := range app.Tasks {
diff --git a/utils/apps.go b/utils/apps.go
index 04aac94..7932085 100644
--- a/utils/apps.go
+++ b/utils/apps.go
@@ -8,24 +8,32 @@ import (
 )
 
 func ConsulApp(name string, instances int) *apps.App {
-	return app(name, instances, true, 0)
+	return app(name, instances, 1, true, 0)
 }
 
 func ConsulAppWithUnhealthyInstances(name string, instances int, unhealthyInstances int) *apps.App {
-	return app(name, instances, true, unhealthyInstances)
+	return app(name, instances, 1, true, unhealthyInstances)
+}
+
+func ConsulAppMultipleRegistrations(name string, instances int, registrations int) *apps.App {
+	return app(name, instances, registrations, true, 0)
 }
 
 func NonConsulApp(name string, instances int) *apps.App {
-	return app(name, instances, false, 0)
+	return app(name, instances, 1, false, 0)
 }
 
-func app(name string, instances int, consul bool, unhealthyInstances int) *apps.App {
+func app(name string, instances int, registrationsPerInstance int, consul bool, unhealthyInstances int) *apps.App {
 	var appTasks []apps.Task
 	for i := 0; i < instances; i++ {
+		var ports []int
+		for j := 1; j <= registrationsPerInstance; j++ {
+			ports = append(ports, 8080+(i*j)+j-1)
+		}
 		task := apps.Task{
-			AppID: apps.AppId(name),
-			ID:    apps.TaskId(fmt.Sprintf("%s.%d", strings.Replace(strings.Trim(name, "/"), "/", "_", -1), i)),
-			Ports: []int{8080 + i},
+			AppID: apps.AppID(name),
+			ID:    apps.TaskID(fmt.Sprintf("%s.%d", strings.Replace(strings.Trim(name, "/"), "/", "_", -1), i)),
+			Ports: ports,
 			Host:  "localhost",
 		}
 		if unhealthyInstances > 0 {
@@ -42,12 +50,22 @@ func app(name string, instances int, consul bool, unhealthyInstances int) *apps.
 
 	labels := make(map[string]string)
 	if consul {
-		labels[apps.MARATHON_CONSUL_LABEL] = "true"
+		labels[apps.MarathonConsulLabel] = "true"
 	}
 
-	return &apps.App{
-		ID:     apps.AppId(name),
+	app := &apps.App{
+		ID:     apps.AppID(name),
 		Tasks:  appTasks,
 		Labels: labels,
 	}
+
+	if registrationsPerInstance > 1 {
+		for i := 0; i < registrationsPerInstance; i++ {
+			app.PortDefinitions = append(app.PortDefinitions, apps.PortDefinition{
+				Labels: map[string]string{"consul": ""},
+			})
+		}
+	}
+
+	return app
 }
diff --git a/utils/errors.go b/utils/errors.go
new file mode 100644
index 0000000..0116de9
--- /dev/null
+++ b/utils/errors.go
@@ -0,0 +1,15 @@
+package utils
+
+import "fmt"
+
+func MergeErrorsOrNil(errors []error, description string) error {
+	if len(errors) == 0 {
+		return nil
+	}
+
+	errMessage := fmt.Sprintf("%d errors occured %s", len(errors), description)
+	for i, err := range errors {
+		errMessage = fmt.Sprintf("%s\n%d: %s", errMessage, i+1, err.Error())
+	}
+	return fmt.Errorf(errMessage)
+}
diff --git a/utils/net.go b/utils/net.go
index e190e04..cc95a3b 100644
--- a/utils/net.go
+++ b/utils/net.go
@@ -1,7 +1,7 @@
 package utils
 
 import (
-	"fmt"
+	"errors"
 	"net"
 )
 
@@ -15,5 +15,5 @@ func HostToIPv4(host string) (net.IP, error) {
 			return IP, nil
 		}
 	}
-	return nil, fmt.Errorf("Could not resolve host to IPv4")
+	return nil, errors.New("Could not resolve host to IPv4")
 }
diff --git a/web/event_handler.go b/web/event_handler.go
index ea28bbf..fd4d613 100644
--- a/web/event_handler.go
+++ b/web/event_handler.go
@@ -7,10 +7,10 @@ import (
 
 	log "github.com/Sirupsen/logrus"
 	"github.com/allegro/marathon-consul/apps"
-	service "github.com/allegro/marathon-consul/consul"
 	"github.com/allegro/marathon-consul/events"
 	"github.com/allegro/marathon-consul/marathon"
 	"github.com/allegro/marathon-consul/metrics"
+	"github.com/allegro/marathon-consul/service"
 )
 
 type event struct {
@@ -20,27 +20,27 @@ type event struct {
 }
 
 type eventHandler struct {
-	id         int
-	service    service.ConsulServices
-	marathon   marathon.Marathoner
-	eventQueue <-chan event
+	id              int
+	serviceRegistry service.ServiceRegistry
+	marathon        marathon.Marathoner
+	eventQueue      <-chan event
 }
 
 type stopEvent struct{}
 
-func newEventHandler(id int, service service.ConsulServices, marathon marathon.Marathoner, eventQueue <-chan event) *eventHandler {
+func newEventHandler(id int, serviceRegistry service.ServiceRegistry, marathon marathon.Marathoner, eventQueue <-chan event) *eventHandler {
 	return &eventHandler{
-		id:         id,
-		service:    service,
-		marathon:   marathon,
-		eventQueue: eventQueue,
+		id:              id,
+		serviceRegistry: serviceRegistry,
+		marathon:        marathon,
+		eventQueue:      eventQueue,
 	}
 }
 
-func (fh *eventHandler) Start() chan<- stopEvent {
-	var event event
+func (fh *eventHandler) start() chan<- stopEvent {
+	var e event
 	process := func() {
-		err := fh.handleEvent(event.eventType, event.body)
+		err := fh.handleEvent(e.eventType, e.body)
 		if err != nil {
 			metrics.Mark("events.processing.error")
 		} else {
@@ -53,11 +53,11 @@ func (fh *eventHandler) Start() chan<- stopEvent {
 	go func() {
 		for {
 			select {
-			case event = <-fh.eventQueue:
+			case e = <-fh.eventQueue:
 				metrics.Mark(fmt.Sprintf("events.handler.%d", fh.id))
 				metrics.UpdateGauge("events.queue.len", int64(len(fh.eventQueue)))
-				metrics.UpdateGauge("events.queue.delay_ns", time.Since(event.timestamp).Nanoseconds())
-				metrics.Time("events.processing."+event.eventType, process)
+				metrics.UpdateGauge("events.queue.delay_ns", time.Since(e.timestamp).Nanoseconds())
+				metrics.Time("events.processing."+e.eventType, process)
 			case <-quitChan:
 				log.WithField("Id", fh.id).Info("Stopping worker")
 			}
@@ -68,19 +68,13 @@ func (fh *eventHandler) Start() chan<- stopEvent {
 
 func (fh *eventHandler) handleEvent(eventType string, body []byte) error {
 
-	body = replaceTaskIdWithId(body)
+	body = replaceTaskIDWithID(body)
 
 	switch eventType {
 	case "status_update_event":
 		return fh.handleStatusEvent(body)
 	case "health_status_changed_event":
 		return fh.handleHealthStatusEvent(body)
-	case "unhealthy_task_kill_event":
-		return fh.handleUnhealthyTaskKillEvent(body)
-	case "deployment_info":
-		return fh.handleDeploymentInfo(body)
-	case "deployment_step_success":
-		return fh.handleDeploymentStepSuccess(body)
 	default:
 		log.WithField("EventType", eventType).Debug("Not handled event type")
 		return nil
@@ -105,8 +99,8 @@ func (fh *eventHandler) handleHealthStatusEvent(body []byte) error {
 		return nil
 	}
 
-	appId := taskHealthChange.AppID
-	app, err := fh.marathon.App(appId)
+	appID := taskHealthChange.AppID
+	app, err := fh.marathon.App(appID)
 	if err != nil {
 		log.WithField("Id", taskHealthChange.ID).WithError(err).Error("There was a problem obtaining app info")
 		return err
@@ -120,24 +114,22 @@ func (fh *eventHandler) handleHealthStatusEvent(body []byte) error {
 
 	tasks := app.Tasks
 
-	task, err := findTaskById(taskHealthChange.ID, tasks)
+	task, err := findTaskByID(taskHealthChange.ID, tasks)
 	if err != nil {
 		log.WithField("Id", taskHealthChange.ID).WithError(err).Error("Task not found")
 		return err
 	}
 
 	if task.IsHealthy() {
-		err := fh.service.Register(&task, app)
+		err := fh.serviceRegistry.Register(&task, app)
 		if err != nil {
 			log.WithField("Id", task.ID).WithError(err).Error("There was a problem registering task")
 			return err
-		} else {
-			return nil
 		}
-	} else {
-		log.WithField("Id", task.ID).Debug("Task is not healthy. Not registering")
 		return nil
 	}
+	log.WithField("Id", task.ID).Debug("Task is not healthy. Not registering")
+	return nil
 }
 
 func (fh *eventHandler) handleStatusEvent(body []byte) error {
@@ -155,7 +147,7 @@ func (fh *eventHandler) handleStatusEvent(body []byte) error {
 
 	switch task.TaskStatus {
 	case "TASK_FINISHED", "TASK_FAILED", "TASK_KILLED", "TASK_LOST":
-		return fh.deregister(task.ID, task.Host)
+		return fh.deregister(task.ID)
 	default:
 		log.WithFields(log.Fields{
 			"Id":         task.ID,
@@ -165,107 +157,16 @@ func (fh *eventHandler) handleStatusEvent(body []byte) error {
 	}
 }
 
-func (fh *eventHandler) handleUnhealthyTaskKillEvent(body []byte) error {
-	task, err := apps.ParseTask(body)
-
-	if err != nil {
-		log.WithError(err).WithField("Body", body).Error("Could not parse event body")
-		return err
-	}
-
-	log.WithFields(log.Fields{
-		"Id": task.ID,
-	}).Info("Got Unhealthy TaskKilled Event")
-
-	return fh.deregister(task.ID, task.Host)
-}
-
-//This handler is used when an application is stopped
-func (fh *eventHandler) handleDeploymentInfo(body []byte) error {
-	deploymentEvent, err := events.ParseDeploymentEvent(body)
-
+func (fh *eventHandler) deregister(taskID apps.TaskID) error {
+	err := fh.serviceRegistry.DeregisterByTask(taskID)
 	if err != nil {
-		log.WithError(err).WithField("Body", body).Error("Could not parse event body")
-		return err
-	}
-
-	errors := []error{}
-	for _, app := range deploymentEvent.StoppedConsulApps() {
-		for _, err = range fh.deregisterAllAppServices(app) {
-			errors = append(errors, err)
-		}
-	}
-	if len(errors) > 0 {
-		return fh.mergeDeregistrationErrors(errors)
-	} else {
-		return nil
-	}
-}
-
-//This handler is used when an application is restarted and renamed
-func (fh *eventHandler) handleDeploymentStepSuccess(body []byte) error {
-	deploymentEvent, err := events.ParseDeploymentEvent(body)
-
-	if err != nil {
-		log.WithError(err).WithField("Body", body).Error("Could not parse event body")
-		return err
-	}
-
-	errors := []error{}
-	for _, app := range deploymentEvent.RenamedConsulApps() {
-		for _, err = range fh.deregisterAllAppServices(app) {
-			errors = append(errors, err)
-		}
-	}
-	if len(errors) > 0 {
-		return fh.mergeDeregistrationErrors(errors)
-	} else {
-		return nil
-	}
-}
-
-func (fh *eventHandler) deregisterAllAppServices(app *apps.App) []error {
-
-	errors := []error{}
-	serviceName := fh.service.ServiceName(app)
-
-	log.WithField("AppId", app.ID).WithField("ServiceName", serviceName).Info("Deregistering all services")
-
-	services, err := fh.service.GetServices(serviceName)
-
-	if err != nil {
-		log.WithField("Id", app.ID).WithError(err).Error("There was a problem getting Consul services")
-		errors = append(errors, err)
-		return errors
-	}
-
-	if len(services) == 0 {
-		log.WithField("AppId", app.ID).WithField("ServiceName", serviceName).Info("No matching Consul services found")
-		return errors
-	}
-
-	for _, service := range services {
-		taskId := apps.TaskId(service.ServiceID)
-		if taskId.AppId() == app.ID {
-			err = fh.deregister(taskId, service.Address)
-			if err != nil {
-				errors = append(errors, err)
-			}
-		}
-	}
-	return errors
-}
-
-func (fh *eventHandler) deregister(serviceId apps.TaskId, agentAddress string) error {
-	err := fh.service.Deregister(serviceId, agentAddress)
-	if err != nil {
-		log.WithField("Id", serviceId).WithError(err).Error("There was a problem deregistering task")
+		log.WithField("Id", taskID).WithError(err).Error("There was a problem deregistering task")
 	}
 	return err
 }
 
-func findTaskById(id apps.TaskId, tasks_ []apps.Task) (apps.Task, error) {
-	for _, task := range tasks_ {
+func findTaskByID(id apps.TaskID, tasks []apps.Task) (apps.Task, error) {
+	for _, task := range tasks {
 		if task.ID == id {
 			return task, nil
 		}
@@ -273,18 +174,10 @@ func findTaskById(id apps.TaskId, tasks_ []apps.Task) (apps.Task, error) {
 	return apps.Task{}, fmt.Errorf("Task %s not found", id)
 }
 
-func (fh *eventHandler) mergeDeregistrationErrors(errors []error) error {
-	errMessage := fmt.Sprintf("%d errors occured handling service deregistration", len(errors))
-	for i, err := range errors {
-		errMessage = fmt.Sprintf("%s\n%d: %s", errMessage, i, err.Error())
-	}
-	return fmt.Errorf(errMessage)
-}
-
 // for every other use of Tasks, Marathon uses the "id" field for the task ID.
 // Here, it uses "taskId", with most of the other fields being equal. We'll
 // just swap "taskId" for "id" in the body so that we can successfully parse
 // incoming events.
-func replaceTaskIdWithId(body []byte) []byte {
+func replaceTaskIDWithID(body []byte) []byte {
 	return bytes.Replace(body, []byte("taskId"), []byte("id"), -1)
 }
diff --git a/web/health_handler.go b/web/health_handler.go
index 9376b22..20ecdb8 100644
--- a/web/health_handler.go
+++ b/web/health_handler.go
@@ -5,6 +5,6 @@ import (
 	"net/http"
 )
 
-func HealthHandler(w http.ResponseWriter, r *http.Request) {
+func HealthHandler(w http.ResponseWriter, _ *http.Request) {
 	fmt.Fprintln(w, "OK")
 }
diff --git a/web/web.go b/web/web.go
index 396c59c..0e9e5e0 100644
--- a/web/web.go
+++ b/web/web.go
@@ -3,20 +3,20 @@ package web
 import (
 	"net/http"
 
-	service "github.com/allegro/marathon-consul/consul"
 	"github.com/allegro/marathon-consul/marathon"
+	"github.com/allegro/marathon-consul/service"
 )
 
 type Stop func()
 type Handler func(w http.ResponseWriter, r *http.Request)
 
-func NewHandler(config Config, marathon marathon.Marathoner, service service.ConsulServices) (Handler, Stop) {
+func NewHandler(config Config, marathon marathon.Marathoner, serviceOperations service.ServiceRegistry) (Handler, Stop) {
 
 	stopChannels := make([]chan<- stopEvent, config.WorkersCount, config.WorkersCount)
 	eventQueue := make(chan event, config.QueueSize)
 	for i := 0; i < config.WorkersCount; i++ {
-		handler := newEventHandler(i, service, marathon, eventQueue)
-		stopChannels[i] = handler.Start()
+		handler := newEventHandler(i, serviceOperations, marathon, eventQueue)
+		stopChannels[i] = handler.start()
 	}
 	return newWebHandler(eventQueue).Handle, stop(stopChannels)
 }
diff --git a/web/web_handler.go b/web/web_handler.go
index 0788504..4439019 100644
--- a/web/web_handler.go
+++ b/web/web_handler.go
@@ -12,15 +12,15 @@ import (
 	"github.com/allegro/marathon-consul/metrics"
 )
 
-type WebHandler struct {
+type EventHandler struct {
 	eventQueue chan event
 }
 
-func newWebHandler(eventQueue chan event) *WebHandler {
-	return &WebHandler{eventQueue: eventQueue}
+func newWebHandler(eventQueue chan event) *EventHandler {
+	return &EventHandler{eventQueue: eventQueue}
 }
 
-func (h *WebHandler) Handle(w http.ResponseWriter, r *http.Request) {
+func (h *EventHandler) Handle(w http.ResponseWriter, r *http.Request) {
 	metrics.Time("events.response", func() {
 		body, err := ioutil.ReadAll(r.Body)
 		if err != nil {


Test patch:
diff --git a/apps/app_test.go b/apps/app_test.go
index 4a8104d..01987c3 100644
--- a/apps/app_test.go
+++ b/apps/app_test.go
@@ -130,5 +130,267 @@ func TestConsulApp(t *testing.T) {
 
 func TestAppId_String(t *testing.T) {
 	t.Parallel()
-	assert.Equal(t, "appId", AppId("appId").String())
+	assert.Equal(t, "appId", AppID("appId").String())
+}
+
+var dummyTask = &Task{
+	ID:    TaskID("some-task"),
+	Ports: []int{1337},
+}
+
+func TestRegistrationIntent_NameWithoutConsulLabel(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID: "/rootGroup/subGroup/subSubGroup/name",
+	}
+
+	// when
+	intent := app.RegistrationIntents(dummyTask, ".")[0]
+
+	// then
+	assert.Equal(t, "rootGroup.subGroup.subSubGroup.name", intent.Name)
+}
+
+func TestRegistrationIntent_Name(t *testing.T) {
+	t.Parallel()
+
+	var intentNameTestsData = []struct {
+		consulLabel  string
+		expectedName string
+	}{
+		{"", "rootGroup-subGroup-subSubGroup-name"},
+		{"true", "rootGroup-subGroup-subSubGroup-name"},
+		{"/some-other/name", "some-other-name"},
+		{"     ///", "rootGroup-subGroup-subSubGroup-name"},
+	}
+
+	for _, testData := range intentNameTestsData {
+		// given
+		app := &App{
+			ID:     "/rootGroup/subGroup/subSubGroup/name",
+			Labels: map[string]string{"consul": testData.consulLabel},
+		}
+
+		// when
+		intent := app.RegistrationIntents(dummyTask, "-")[0]
+
+		// then
+		if intent.Name != testData.expectedName {
+			t.Errorf("Registration name from consul label '%s' was '%s', expected '%s'", testData.consulLabel, intent.Name, testData.expectedName)
+		}
+	}
+}
+
+func TestRegistrationIntent_PickFirstPort(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID: "name",
+	}
+	task := &Task{
+		Ports: []int{1234, 5678},
+	}
+
+	// when
+	intent := app.RegistrationIntents(task, "-")[0]
+
+	// then
+	assert.Equal(t, 1234, intent.Port)
+}
+
+func TestRegistrationIntent_WithTags(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "name",
+		Labels: map[string]string{"private": "tag", "other": "irrelevant"},
+	}
+
+	// when
+	intent := app.RegistrationIntents(dummyTask, "-")[0]
+
+	// then
+	assert.Equal(t, []string{"private"}, intent.Tags)
+}
+
+func TestRegistrationIntent_NoOverrideViaPortDefinitionsIfNoConsulLabelThere(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "app-name",
+		Labels: map[string]string{"consul": "true", "private": "tag"},
+		PortDefinitions: []PortDefinition{
+			{
+				Labels: map[string]string{"other": "tag"},
+			},
+			{},
+		},
+	}
+	task := &Task{
+		Ports: []int{1234, 5678},
+	}
+
+	// when
+	intents := app.RegistrationIntents(task, "-")
+
+	// then
+	assert.Len(t, intents, 1)
+	assert.Equal(t, "app-name", intents[0].Name)
+	assert.Equal(t, 1234, intents[0].Port)
+	assert.Equal(t, []string{"private"}, intents[0].Tags)
+}
+
+func TestRegistrationIntent_OverrideNameAndAddTagsViaPortDefinitions(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "app-name",
+		Labels: map[string]string{"consul": "true", "private": "tag"},
+		PortDefinitions: []PortDefinition{
+			{
+				Labels: map[string]string{"consul": "other-name", "other": "tag"},
+			},
+			{},
+		},
+	}
+	task := &Task{
+		Ports: []int{1234, 5678},
+	}
+
+	// when
+	intents := app.RegistrationIntents(task, "-")
+
+	// then
+	assert.Len(t, intents, 1)
+	assert.Equal(t, "other-name", intents[0].Name)
+	assert.Equal(t, 1234, intents[0].Port)
+	assert.Equal(t, []string{"private", "other"}, intents[0].Tags)
+}
+
+func TestRegistrationIntent_PickDifferentPortViaPortDefinitions(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "app-name",
+		Labels: map[string]string{"consul": "true", "private": "tag"},
+		PortDefinitions: []PortDefinition{
+			{},
+			{
+				Labels: map[string]string{"consul": "true"},
+			},
+		},
+	}
+	task := &Task{
+		Ports: []int{1234, 5678},
+	}
+
+	// when
+	intent := app.RegistrationIntents(task, "-")[0]
+
+	// then
+	assert.Equal(t, 5678, intent.Port)
+}
+
+func TestRegistrationIntent_MultipleIntentsViaPortDefinitionIfMultipleContainConsulLabel(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "app-name",
+		Labels: map[string]string{"consul": "true", "common-tag": "tag"},
+		PortDefinitions: []PortDefinition{
+			{
+				Labels: map[string]string{"consul": "first-name", "first-tag": "tag"},
+			},
+			{
+				Labels: map[string]string{"consul": "second-name", "second-tag": "tag"},
+			},
+		},
+	}
+	task := &Task{
+		Ports: []int{1234, 5678},
+	}
+
+	// when
+	intents := app.RegistrationIntents(task, "-")
+
+	// then
+	assert.Len(t, intents, 2)
+	assert.Equal(t, "first-name", intents[0].Name)
+	assert.Equal(t, 1234, intents[0].Port)
+	assert.Equal(t, []string{"common-tag", "first-tag"}, intents[0].Tags)
+	assert.Equal(t, "second-name", intents[1].Name)
+	assert.Equal(t, 5678, intents[1].Port)
+	assert.Equal(t, []string{"common-tag", "second-tag"}, intents[1].Tags)
+}
+
+func TestRegistrationIntentsNumber_NotConsulApp(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID: "id",
+	}
+
+	// expect
+	assert.Equal(t, 0, app.RegistrationIntentsNumber())
+}
+
+func TestRegistrationIntentsNumber_NoPortDefinitions(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "id",
+		Labels: map[string]string{"consul": ""},
+	}
+
+	// expect
+	assert.Equal(t, 1, app.RegistrationIntentsNumber())
+}
+
+func TestRegistrationIntentsNumber_SinglePortDefinitions(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "id",
+		Labels: map[string]string{"consul": ""},
+		PortDefinitions: []PortDefinition{
+			{
+				Labels: map[string]string{"consul": ""},
+			},
+		},
+	}
+
+	// expect
+	assert.Equal(t, 1, app.RegistrationIntentsNumber())
+}
+
+func TestRegistrationIntentsNumber_MultiplePortDefinitions(t *testing.T) {
+	t.Parallel()
+
+	// given
+	app := &App{
+		ID:     "id",
+		Labels: map[string]string{"consul": ""},
+		PortDefinitions: []PortDefinition{
+			{
+				Labels: map[string]string{"consul": ""},
+			},
+			{
+				Labels: map[string]string{"consul": ""},
+			},
+		},
+	}
+
+	// expect
+	assert.Equal(t, 2, app.RegistrationIntentsNumber())
 }
diff --git a/apps/task_test.go b/apps/task_test.go
index a08a10c..da48621 100644
--- a/apps/task_test.go
+++ b/apps/task_test.go
@@ -106,16 +106,16 @@ func TestIsHealthy(t *testing.T) {
 
 func TestId_String(t *testing.T) {
 	t.Parallel()
-	assert.Equal(t, "id", TaskId("id").String())
+	assert.Equal(t, "id", TaskID("id").String())
 }
 
 func TestId_AppId(t *testing.T) {
 	t.Parallel()
 	id := "pl.allegro_test_app.a7cde60e-0093-11e6-ab55-02aab772a161"
-	assert.Equal(t, AppId("/pl.allegro/test/app"), TaskId(id).AppId())
+	assert.Equal(t, AppID("/pl.allegro/test/app"), TaskID(id).AppID())
 }
 
 func TestId_AppIdForInvalid(t *testing.T) {
 	t.Parallel()
-	assert.Panics(t, func() { TaskId("id").AppId() })
+	assert.Panics(t, func() { TaskID("id").AppID() })
 }
diff --git a/config/config_test.go b/config/config_test.go
index 7ee4048..2ef4506 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -85,7 +85,7 @@ func TestConfig_ShouldUseTextFormatterWhenFormatterIsUnknown(t *testing.T) {
 func TestConfig_ShouldBeMergedWithFileDefaultsAndFlags(t *testing.T) {
 	clear()
 	expected := &Config{
-		Consul: consul.ConsulConfig{
+		Consul: consul.Config{
 			Auth: consul.Auth{Enabled: false,
 				Username: "",
 				Password: ""},
diff --git a/consul/agents_test.go b/consul/agents_test.go
index 438dd20..bad8cbe 100644
--- a/consul/agents_test.go
+++ b/consul/agents_test.go
@@ -10,7 +10,7 @@ import (
 func TestGetAgent(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	agent, err := agents.GetAgent("127.0.0.1")
@@ -23,7 +23,7 @@ func TestGetAgent(t *testing.T) {
 func TestGetAnyAgent_SingleAgentAvailable(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	agents.GetAgent("127.0.0.1") // create
@@ -38,7 +38,7 @@ func TestGetAnyAgent_SingleAgentAvailable(t *testing.T) {
 func TestGetAnyAgent(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 	agent1, _ := agents.GetAgent("127.0.0.1")
 	agent2, _ := agents.GetAgent("127.0.0.2")
 	agent3, _ := agents.GetAgent("127.0.0.3")
@@ -54,7 +54,7 @@ func TestGetAnyAgent(t *testing.T) {
 func TestGetAgent_ShouldResolveAddressToIP(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	agent1, _ := agents.GetAgent("127.0.0.1")
@@ -67,7 +67,7 @@ func TestGetAgent_ShouldResolveAddressToIP(t *testing.T) {
 func TestGetAgent_ShouldFailOnWrongHostname(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	_, err := agents.GetAgent("wrong hostname")
@@ -79,7 +79,7 @@ func TestGetAgent_ShouldFailOnWrongHostname(t *testing.T) {
 func TestGetAgent_ShouldFailOnUnknownHostname(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	_, err := agents.GetAgent("unknown.host.name.1")
@@ -91,7 +91,7 @@ func TestGetAgent_ShouldFailOnUnknownHostname(t *testing.T) {
 func TestGetAnyAgent_shouldFailOnNoAgentAvailable(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 
 	// when
 	anyAgent, err := agents.GetAnyAgent()
@@ -104,7 +104,7 @@ func TestGetAnyAgent_shouldFailOnNoAgentAvailable(t *testing.T) {
 func TestRemoveAgent(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 	agents.GetAgent("127.0.0.1")
 	agent2, _ := agents.GetAgent("127.0.0.2")
 
@@ -123,7 +123,7 @@ func TestRemoveAgent(t *testing.T) {
 func TestRemoveAgentTwiceShouldPass(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 	agents.GetAgent("127.0.0.1")
 
 	// when
diff --git a/consul/consul_stub_test.go b/consul/consul_stub_test.go
index 76eaed6..935916a 100644
--- a/consul/consul_stub_test.go
+++ b/consul/consul_stub_test.go
@@ -1,9 +1,9 @@
 package consul
 
 import (
-	"fmt"
 	"testing"
 
+	"github.com/allegro/marathon-consul/apps"
 	"github.com/allegro/marathon-consul/utils"
 	"github.com/stretchr/testify/assert"
 )
@@ -13,7 +13,6 @@ func TestConsulStub(t *testing.T) {
 	// given
 	consul := NewConsulStub()
 	app := utils.ConsulApp("test", 3)
-	stubError := fmt.Errorf("Some error")
 	services, err := consul.GetAllServices()
 	assert.NoError(t, err)
 	testServices, err := consul.GetServices("test")
@@ -38,37 +37,40 @@ func TestConsulStub(t *testing.T) {
 	assert.Len(t, testServices, 3)
 
 	// when
-	err = consul.Deregister(app.Tasks[1].ID, "")
+	err = consul.DeregisterByTask(app.Tasks[1].ID)
 	services, _ = consul.GetAllServices()
-	servicesIds := consul.RegisteredServicesIds()
+	taskIds := consul.RegisteredTaskIDs()
 
 	// then
 	assert.NoError(t, err)
 	assert.Len(t, services, 2)
-	assert.Contains(t, servicesIds, "test.0")
-	assert.Contains(t, servicesIds, "test.2")
+	assert.Contains(t, taskIds, apps.TaskID("test.0"))
+	assert.Contains(t, taskIds, apps.TaskID("test.2"))
 
 	// given
-	consul.ErrorServices[app.Tasks[0].ID] = stubError
+	consul.FailDeregisterByTaskForID(app.Tasks[0].ID)
 
 	// when
-	err = consul.Deregister(app.Tasks[0].ID, "")
+	err = consul.DeregisterByTask(app.Tasks[0].ID)
 
 	// then
-	assert.Equal(t, stubError, err)
+	assert.Error(t, err)
+
+	// given
+	consul.FailRegisterForID(app.Tasks[0].ID)
 
 	// when
 	err = consul.Register(&app.Tasks[0], app)
 
 	// then
-	assert.Equal(t, stubError, err)
+	assert.Error(t, err)
 
 	// when
-	err = consul.Deregister(app.Tasks[2].ID, "")
+	err = consul.DeregisterByTask(app.Tasks[2].ID)
 
 	// then
 	assert.NoError(t, err)
-	assert.Len(t, consul.RegisteredServicesIds(), 1)
+	assert.Len(t, consul.RegisteredTaskIDs(), 1)
 
 	// when
 	app = utils.ConsulApp("other", 2)
@@ -80,7 +82,7 @@ func TestConsulStub(t *testing.T) {
 	otherServices, _ := consul.GetServices("other")
 
 	// then
-	assert.Len(t, consul.RegisteredServicesIds(), 3)
+	assert.Len(t, services, 3)
 	assert.Len(t, testServices, 1)
 	assert.Len(t, otherServices, 2)
 }
diff --git a/consul/consul_test.go b/consul/consul_test.go
index 4013ce3..c62be6f 100644
--- a/consul/consul_test.go
+++ b/consul/consul_test.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/allegro/marathon-consul/apps"
+	"github.com/allegro/marathon-consul/service"
 	"github.com/allegro/marathon-consul/utils"
 	consulapi "github.com/hashicorp/consul/api"
 	"github.com/stretchr/testify/assert"
@@ -14,7 +15,7 @@ import (
 func TestGetAgent_WithEmptyHost(t *testing.T) {
 	t.Parallel()
 	// given
-	agents := NewAgents(&ConsulConfig{})
+	agents := NewAgents(&Config{})
 	// when
 	agent, err := agents.GetAgent("")
 	// then
@@ -26,7 +27,7 @@ func TestGetAgent_FullConfig(t *testing.T) {
 	t.Parallel()
 
 	// given
-	agents := NewAgents(&ConsulConfig{Token: "token", SslEnabled: true,
+	agents := NewAgents(&Config{Token: "token", SslEnabled: true,
 		Auth: Auth{Enabled: true, Username: "", Password: ""}, Timeout: time.Second})
 
 	// when
@@ -41,7 +42,7 @@ func TestGetAllServices_ForEmptyAgents(t *testing.T) {
 	t.Parallel()
 
 	// given
-	consul := New(ConsulConfig{})
+	consul := New(Config{})
 
 	// when
 	services, err := consul.GetAllServices()
@@ -55,7 +56,7 @@ func TestRegister_ForInvalidHost(t *testing.T) {
 	t.Parallel()
 
 	// given
-	consul := New(ConsulConfig{})
+	consul := New(Config{})
 	app := utils.ConsulApp("serviceA", 1)
 
 	// when
@@ -65,7 +66,12 @@ func TestRegister_ForInvalidHost(t *testing.T) {
 	assert.Error(t, err)
 
 	// when
-	err = consul.Deregister("service", "")
+	err = consul.Deregister(&service.Service{
+		ID:   service.ServiceId("someId"),
+		Name: "service",
+		Tags: []string{},
+		RegisteringAgentAddress: "",
+	})
 
 	// then
 	assert.Error(t, err)
@@ -74,16 +80,16 @@ func TestRegister_ForInvalidHost(t *testing.T) {
 func TestGetServices(t *testing.T) {
 	t.Parallel()
 	// create cluster of 2 consul servers
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
-	server2 := CreateConsulTestServer(t)
+	server2 := CreateTestServer(t)
 	defer server2.Stop()
 
 	server1.JoinWAN(server2.LANAddr)
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -104,7 +110,7 @@ func TestGetServices(t *testing.T) {
 
 	serviceNames := make(map[string]struct{})
 	for _, s := range services {
-		serviceNames[s.ServiceName] = struct{}{}
+		serviceNames[s.Name] = struct{}{}
 	}
 	assert.Len(t, serviceNames, 1)
 	assert.Contains(t, serviceNames, "serviceA")
@@ -112,11 +118,11 @@ func TestGetServices(t *testing.T) {
 
 func TestGetService_FailingAgent_GivingUp(t *testing.T) {
 	t.Parallel()
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
 	// create client
-	consul := FailingConsulClient()
+	consul := FailingClient()
 
 	// when
 	services, err := consul.GetServices("serviceA")
@@ -129,11 +135,11 @@ func TestGetService_FailingAgent_GivingUp(t *testing.T) {
 func TestGetServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 	t.Parallel()
 	// create server
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.Tag = "marathon"
 	consul.config.RequestRetries = 100
 
@@ -143,7 +149,7 @@ func TestGetServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 
 	// add failing clients
 	for i := uint32(2); i < consul.config.RequestRetries; i++ {
-		consul.GetAgent(fmt.Sprintf("127.0.0.%d", i))
+		consul.AddAgent(fmt.Sprintf("127.0.0.%d", i))
 	}
 
 	// when
@@ -157,16 +163,16 @@ func TestGetServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 func TestGetServices_SelectOnlyTaggedServices(t *testing.T) {
 	t.Parallel()
 	// create cluster of 2 consul servers
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
-	server2 := CreateConsulTestServer(t)
+	server2 := CreateTestServer(t)
 	defer server2.Stop()
 
 	server1.JoinWAN(server2.LANAddr)
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.Tag = "marathon-mycluster"
 
 	// given
@@ -184,11 +190,11 @@ func TestGetServices_SelectOnlyTaggedServices(t *testing.T) {
 	// then
 	assert.NoError(t, err)
 	assert.Len(t, services, 1)
-	assert.Contains(t, services[0].ServiceTags, "marathon-mycluster")
+	assert.Contains(t, services[0].Tags, "marathon-mycluster")
 
 	serviceNames := make(map[string]struct{})
 	for _, s := range services {
-		serviceNames[s.ServiceName] = struct{}{}
+		serviceNames[s.Name] = struct{}{}
 	}
 	assert.Len(t, serviceNames, 1)
 	assert.Contains(t, serviceNames, "serviceA")
@@ -197,16 +203,16 @@ func TestGetServices_SelectOnlyTaggedServices(t *testing.T) {
 func TestGetAllServices(t *testing.T) {
 	t.Parallel()
 	// create cluster of 2 consul servers
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
-	server2 := CreateConsulTestServer(t)
+	server2 := CreateTestServer(t)
 	defer server2.Stop()
 
 	server1.JoinWAN(server2.LANAddr)
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -227,7 +233,7 @@ func TestGetAllServices(t *testing.T) {
 
 	serviceNames := make(map[string]struct{})
 	for _, s := range services {
-		serviceNames[s.ServiceName] = struct{}{}
+		serviceNames[s.Name] = struct{}{}
 	}
 	assert.Len(t, serviceNames, 2)
 	assert.Contains(t, serviceNames, "serviceA")
@@ -236,22 +242,22 @@ func TestGetAllServices(t *testing.T) {
 
 func TestGetServicesUsingProviderWithRetriesOnAgentFailure_ShouldRetryConfiguredNumberOfTimes(t *testing.T) {
 	t.Parallel()
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.AgentFailuresTolerance = 2
 	consul.config.RequestRetries = 4
 
 	var called uint32
-	provider := func(agent *consulapi.Client) ([]*consulapi.CatalogService, error) {
+	provider := func(agent *consulapi.Client) ([]*service.Service, error) {
 		called++
 		return nil, fmt.Errorf("error")
 	}
 
 	// add failing client
-	consul.GetAgent("127.0.0.2")
+	consul.AddAgent("127.0.0.2")
 
 	// when
 	consul.getServicesUsingProviderWithRetriesOnAgentFailure(provider)
@@ -263,11 +269,11 @@ func TestGetServicesUsingProviderWithRetriesOnAgentFailure_ShouldRetryConfigured
 
 func TestGetAllServices_FailingAgent_GivingUp(t *testing.T) {
 	t.Parallel()
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
 	// create client
-	consul := FailingConsulClient()
+	consul := FailingClient()
 
 	// when
 	services, err := consul.GetAllServices()
@@ -280,22 +286,22 @@ func TestGetAllServices_FailingAgent_GivingUp(t *testing.T) {
 func TestGetAllServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 	t.Parallel()
 	// create cluster of 2 consul servers
-	server1 := CreateConsulTestServer(t)
+	server1 := CreateTestServer(t)
 	defer server1.Stop()
 
-	server2 := CreateConsulTestServer(t)
+	server2 := CreateTestServer(t)
 	defer server2.Stop()
 
 	server1.JoinWAN(server2.LANAddr)
 
 	// create client
-	consul := ConsulClientAtServer(server1)
+	consul := ClientAtServer(server1)
 	consul.config.Tag = "marathon"
 	consul.config.RequestRetries = 100
 
 	// add failing clients
 	for i := uint32(2); i < consul.config.RequestRetries; i++ {
-		consul.GetAgent(fmt.Sprintf("127.0.0.%d", i))
+		consul.AddAgent(fmt.Sprintf("127.0.0.%d", i))
 	}
 
 	// given
@@ -316,7 +322,7 @@ func TestGetAllServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 
 	serviceNames := make(map[string]struct{})
 	for _, s := range services {
-		serviceNames[s.ServiceName] = struct{}{}
+		serviceNames[s.Name] = struct{}{}
 	}
 	assert.Len(t, serviceNames, 2)
 	assert.Contains(t, serviceNames, "serviceA")
@@ -325,10 +331,10 @@ func TestGetAllServices_RemovingFailingAgentsAndRetrying(t *testing.T) {
 
 func TestRegisterServices(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 	defer server.Stop()
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -347,16 +353,16 @@ func TestRegisterServices(t *testing.T) {
 
 	// then
 	assert.Len(t, services, 1)
-	assert.Equal(t, "serviceA", services[0].ServiceName)
-	assert.Equal(t, []string{"marathon", "test"}, services[0].ServiceTags)
+	assert.Equal(t, "serviceA", services[0].Name)
+	assert.Equal(t, []string{"marathon", "test", "marathon-task:serviceA.0"}, services[0].Tags)
 }
 
 func TestRegisterServices_CustomServiceName(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 	defer server.Stop()
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -376,16 +382,69 @@ func TestRegisterServices_CustomServiceName(t *testing.T) {
 
 	// then
 	assert.Len(t, services, 1)
-	assert.Equal(t, "myCustomServiceName", services[0].ServiceName)
-	assert.Equal(t, []string{"marathon", "test"}, services[0].ServiceTags)
+	assert.Equal(t, "myCustomServiceName", services[0].Name)
+}
+
+func TestRegisterServices_MultipleRegistrations(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+	defer server.Stop()
+
+	consul := ClientAtServer(server)
+	consul.config.Tag = "marathon"
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	app.PortDefinitions = []apps.PortDefinition{
+		{
+			Labels: map[string]string{"consul": "first-name", "first-tag": "tag"},
+		},
+		{
+			Labels: map[string]string{"consul": "second-name", "second-tag": "tag"},
+		},
+	}
+	app.Tasks[0].Host = server.Config.Bind
+	app.Tasks[0].Ports = []int{8080, 8081}
+	app.Labels["common-tag"] = "tag"
+
+	// when
+	err := consul.Register(&app.Tasks[0], app)
+
+	// then
+	assert.NoError(t, err)
+
+	// when
+	services, _ := consul.GetAllServices()
+
+	// then
+	assert.Len(t, services, 2)
+
+	first, found := findServiceByName("first-name", services)
+	assert.True(t, found, "first-name not found in services")
+	second, found := findServiceByName("second-name", services)
+	assert.True(t, found, "second-name not found in services")
+
+	assert.Equal(t, "first-name", first.Name)
+	assert.Equal(t, []string{"marathon", "common-tag", "first-tag", "marathon-task:serviceA.0"}, first.Tags)
+	assert.Equal(t, "second-name", second.Name)
+	assert.Equal(t, []string{"marathon", "common-tag", "second-tag", "marathon-task:serviceA.0"}, second.Tags)
+}
+
+func findServiceByName(name string, services []*service.Service) (*service.Service, bool) {
+	for _, s := range services {
+		if s.Name == name {
+			return s, true
+		}
+	}
+	return nil, false
 }
 
 func TestRegisterServices_InvalidHostnameShouldFail(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 	defer server.Stop()
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -402,10 +461,10 @@ func TestRegisterServices_InvalidHostnameShouldFail(t *testing.T) {
 
 func TestRegisterServices_InvalidCustomServiceName(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 	defer server.Stop()
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -425,15 +484,14 @@ func TestRegisterServices_InvalidCustomServiceName(t *testing.T) {
 
 	// then
 	assert.Len(t, services, 1)
-	assert.Equal(t, "serviceA", services[0].ServiceName)
-	assert.Equal(t, []string{"marathon", "test"}, services[0].ServiceTags)
+	assert.Equal(t, "serviceA", services[0].Name)
 }
 
 func TestRegisterServices_shouldReturnErrorOnFailure(t *testing.T) {
 	t.Parallel()
 
 	// given
-	consul := New(ConsulConfig{Port: "1234"})
+	consul := New(Config{Port: "1234"})
 	app := utils.ConsulApp("serviceA", 1)
 
 	// when
@@ -445,10 +503,10 @@ func TestRegisterServices_shouldReturnErrorOnFailure(t *testing.T) {
 
 func TestDeregisterServices(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 	defer server.Stop()
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
@@ -458,37 +516,191 @@ func TestDeregisterServices(t *testing.T) {
 	assert.Len(t, services, 2)
 
 	// when
-	consul.Deregister("serviceA", server.Config.Bind)
+	servicesA, _ := consul.GetServices("serviceA")
+	consul.Deregister(servicesA[0])
 
 	// then
 	services, _ = consul.GetAllServices()
 	assert.Len(t, services, 1)
-	assert.Equal(t, "serviceB", services[0].ServiceName)
+	assert.Equal(t, "serviceB", services[0].Name)
 }
 
 func TestDeregisterServices_shouldReturnErrorOnFailure(t *testing.T) {
 	t.Parallel()
-	server := CreateConsulTestServer(t)
+	server := CreateTestServer(t)
 
-	consul := ConsulClientAtServer(server)
+	consul := ClientAtServer(server)
 	consul.config.Tag = "marathon"
 
 	// given
 	server.AddService("serviceA", "passing", []string{"marathon"})
 
 	// when
+	servicesA, _ := consul.GetServices("serviceA")
 	server.Stop()
-	err := consul.Deregister("serviceA", server.Config.Bind)
+	err := consul.Deregister(servicesA[0])
 
 	// then
 	assert.Error(t, err)
 }
 
+func TestDeregisterServicesByTask(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+	defer server.Stop()
+
+	consul := ClientAtServer(server)
+	consul.config.Tag = "marathon"
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	task := app.Tasks[0]
+
+	server.AddService("serviceA", "passing", []string{"marathon", service.MarathonTaskTag(task.ID)})
+	server.AddService("serviceB", "passing", []string{"marathon", service.MarathonTaskTag(apps.TaskID("other"))})
+	services, _ := consul.GetAllServices()
+	assert.Len(t, services, 2)
+
+	// when
+	consul.DeregisterByTask(task.ID)
+
+	// then
+	services, _ = consul.GetAllServices()
+	assert.Len(t, services, 1)
+	assert.Equal(t, "serviceB", services[0].Name)
+}
+
+func TestDeregisterServicesByTask_shouldReturnErrorOnFailure(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+
+	consul := ClientAtServer(server)
+	consul.config.Tag = "marathon"
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	task := app.Tasks[0]
+
+	server.AddService("serviceA", "passing", []string{"marathon", service.MarathonTaskTag(task.ID)})
+
+	// when
+	server.Stop()
+	err := consul.DeregisterByTask(task.ID)
+
+	// then
+	assert.Error(t, err)
+}
+
+func TestDeregisterServicesByTask_shouldReturnErrorOnServiceMatchingTaskNotFound(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+	defer server.Stop()
+
+	consul := ClientAtServer(server)
+	consul.config.Tag = "marathon"
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	task := app.Tasks[0]
+
+	server.AddService("serviceA", "passing", []string{"marathon", service.MarathonTaskTag(task.ID)})
+	server.AddService("serviceB", "passing", []string{"marathon", service.MarathonTaskTag(apps.TaskID("other"))})
+	services, _ := consul.GetAllServices()
+	assert.Len(t, services, 2)
+
+	// when
+	err := consul.DeregisterByTask("non-existing")
+
+	// then
+	assert.Error(t, err)
+	services, _ = consul.GetAllServices()
+	assert.Len(t, services, 2)
+}
+
+func TestDeregisterServicesByTask_shouldDeregisterAllMatchingServicesWhenMultipleMatchGivenTaskId(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+	defer server.Stop()
+
+	consul := ClientAtServer(server)
+	consul.config.Tag = "marathon"
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	task := app.Tasks[0]
+
+	server.AddService("serviceA", "passing", []string{"marathon", service.MarathonTaskTag(task.ID)})
+	server.AddService("serviceA-bis", "passing", []string{"marathon", service.MarathonTaskTag(task.ID)})
+	server.AddService("serviceB", "passing", []string{"marathon", service.MarathonTaskTag(apps.TaskID("other"))})
+	services, _ := consul.GetAllServices()
+	assert.Len(t, services, 3)
+
+	// when
+	err := consul.DeregisterByTask(task.ID)
+
+	// then
+	assert.NoError(t, err)
+	services, _ = consul.GetAllServices()
+	assert.Len(t, services, 1)
+}
+
+func TestAddAgentsFromApp(t *testing.T) {
+	t.Parallel()
+	server := CreateTestServer(t)
+	defer server.Stop()
+
+	// create consul without any agents in cache
+	consul := New(Config{
+		Timeout:             10 * time.Millisecond,
+		Port:                fmt.Sprintf("%d", server.Config.Ports.HTTP),
+		ConsulNameSeparator: ".",
+		Tag:                 "marathon",
+	})
+
+	// given
+	app := utils.ConsulApp("serviceA", 1)
+	app.Tasks[0].Host = server.Config.Bind
+	server.AddService("serviceA", "passing", []string{"marathon", service.MarathonTaskTag(app.Tasks[0].ID)})
+
+	// when
+	consul.AddAgentsFromApps([]*apps.App{app, utils.NonConsulApp("nonConsulApp", 1)})
+
+	// then
+	services, err := consul.GetAllServices()
+	assert.Len(t, services, 1)
+	assert.NoError(t, err)
+}
+
+func TestGetIgnoredHealthCheckTypes(t *testing.T) {
+	t.Parallel()
+
+	// given
+	var ignoredTypes = []struct {
+		config string
+		parsed []string
+	}{
+		{",command ", []string{"COMMAND"}},
+		{"tcp,http", []string{"TCP", "HTTP"}},
+		{"tcp, command,", []string{"TCP", "COMMAND"}},
+		{"HTTP ", []string{"HTTP"}},
+		{"", []string{}},
+		{" ,", []string{}},
+	}
+
+	for _, types := range ignoredTypes {
+		// when
+		consul := New(Config{IgnoredHealthChecks: types.config})
+
+		// then
+		assert.Equal(t, types.parsed, consul.getIgnoredHealthCheckTypes())
+	}
+}
+
 func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 	t.Parallel()
 
 	// given
-	consul := New(ConsulConfig{Tag: "marathon"})
+	consul := New(Config{Tag: "marathon"})
 	app := &apps.App{
 		ID: "someApp",
 		HealthChecks: []apps.HealthCheck{
@@ -561,12 +773,13 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 	}
 
 	// when
-	service, err := consul.marathonTaskToConsulService(task, app)
+	services, err := consul.marathonTaskToConsulServices(task, app)
+	service := services[0]
 
 	// then
 	assert.NoError(t, err)
 	assert.Equal(t, "127.0.0.6", service.Address)
-	assert.Equal(t, []string{"marathon", "public"}, service.Tags)
+	assert.Equal(t, []string{"marathon", "public", "marathon-task:someTask"}, service.Tags)
 	assert.Equal(t, 8090, service.Port)
 	assert.Nil(t, service.Check)
 	assert.Equal(t, 5, len(service.Checks))
@@ -576,46 +789,91 @@ func TestMarathonTaskToConsulServiceMapping(t *testing.T) {
 			HTTP:     "http://127.0.0.6:8123/api/health?with=query",
 			Interval: "60s",
 			Timeout:  "20s",
+			Status:   "passing",
 		},
 		{
 			HTTP:     "https://127.0.0.6:8090/secure/health?with=query",
 			Interval: "50s",
 			Timeout:  "20s",
+			Status:   "passing",
 		},
 		{
 			TCP:      "127.0.0.6:8443",
 			Interval: "40s",
 			Timeout:  "20s",
+			Status:   "passing",
 		},
 		{
 			TCP:      "127.0.0.6:8234",
 			Interval: "40s",
 			Timeout:  "20s",
+			Status:   "passing",
 		},
 		{
 			Script:   "echo 1",
 			Interval: "30s",
 			Timeout:  "20s",
+			Status:   "passing",
 		},
 	}, service.Checks)
 }
 
-func TestMarathonTaskToConsulServiceMapping_NotResolvableTaskHost(t *testing.T) {
+func TestMarathonTaskToConsulServiceMapping_IgnoredHealthcheckTypes(t *testing.T) {
 	t.Parallel()
 
 	// given
-	consul := New(ConsulConfig{Tag: "marathon"})
+	consul := New(Config{Tag: "marathon", IgnoredHealthChecks: "command,tcp"})
 	app := &apps.App{
 		ID: "someApp",
 		HealthChecks: []apps.HealthCheck{
 			{
-				Path:                   "/api/health",
+				Path:                   "/api/health?with=query",
+				Protocol:               "HTTP",
+				PortIndex:              0,
+				IntervalSeconds:        60,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
+			{
+				Path:                   "",
 				Protocol:               "HTTP",
 				PortIndex:              0,
 				IntervalSeconds:        60,
 				TimeoutSeconds:         20,
 				MaxConsecutiveFailures: 3,
 			},
+			{
+				Path:                   "/api/health?with=query",
+				Protocol:               "INVALID_PROTOCOL",
+				PortIndex:              0,
+				IntervalSeconds:        60,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
+			{
+				Path:                   "/secure/health?with=query",
+				Protocol:               "HTTPS",
+				PortIndex:              0,
+				IntervalSeconds:        50,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
+			{
+				Protocol:               "TCP",
+				PortIndex:              1,
+				IntervalSeconds:        40,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
+			{
+				Protocol: "COMMAND",
+				Command: struct {
+					Value string `json:"value"`
+				}{Value: "echo 1"},
+				IntervalSeconds:        30,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
 		},
 		Labels: map[string]string{
 			"consul": "true",
@@ -625,97 +883,70 @@ func TestMarathonTaskToConsulServiceMapping_NotResolvableTaskHost(t *testing.T)
 	task := &apps.Task{
 		ID:    "someTask",
 		AppID: app.ID,
-		Host:  "invalid.localhost",
+		Host:  "127.0.0.6",
 		Ports: []int{8090, 8443},
 	}
 
 	// when
-	_, err := consul.marathonTaskToConsulService(task, app)
-
-	// then
-	assert.Error(t, err)
-}
-
-func TestServiceName_WithSerarator(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := &apps.App{
-		ID: "/rootGroup/subGroup/subSubGroup/name",
-	}
-	consul := New(ConsulConfig{ConsulNameSeparator: "."})
-
-	// when
-	serviceName := consul.ServiceName(app)
+	services, err := consul.marathonTaskToConsulServices(task, app)
+	service := services[0]
 
 	// then
-	assert.Equal(t, "rootGroup.subGroup.subSubGroup.name", serviceName)
-}
-
-func TestServiceName_WithEmptyConsulLabel(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := &apps.App{
-		ID:     "/rootGroup/subGroup/subSubGroup/name",
-		Labels: map[string]string{"consul": ""},
-	}
-	consul := New(ConsulConfig{ConsulNameSeparator: "-"})
-
-	// when
-	serviceName := consul.ServiceName(app)
-
-	// then
-	assert.Equal(t, "rootGroup-subGroup-subSubGroup-name", serviceName)
-}
-
-func TestServiceName_WithConsulLabelSetToTrue(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := &apps.App{
-		ID:     "/rootGroup/subGroup/subSubGroup/name",
-		Labels: map[string]string{"consul": "true"},
-	}
-	consul := New(ConsulConfig{ConsulNameSeparator: "-"})
-
-	// when
-	serviceName := consul.ServiceName(app)
+	assert.NoError(t, err)
+	assert.Equal(t, "127.0.0.6", service.Address)
+	assert.Equal(t, []string{"marathon", "public", "marathon-task:someTask"}, service.Tags)
+	assert.Equal(t, 8090, service.Port)
+	assert.Nil(t, service.Check)
+	assert.Equal(t, 2, len(service.Checks))
 
-	// then
-	assert.Equal(t, "rootGroup-subGroup-subSubGroup-name", serviceName)
+	assert.Equal(t, consulapi.AgentServiceChecks{
+		{
+			HTTP:     "http://127.0.0.6:8090/api/health?with=query",
+			Interval: "60s",
+			Timeout:  "20s",
+			Status:   "passing",
+		},
+		{
+			HTTP:     "https://127.0.0.6:8090/secure/health?with=query",
+			Interval: "50s",
+			Timeout:  "20s",
+			Status:   "passing",
+		},
+	}, service.Checks)
 }
 
-func TestServiceName_WithCustomConsulLabelEscapingChars(t *testing.T) {
+func TestMarathonTaskToConsulServiceMapping_NotResolvableTaskHost(t *testing.T) {
 	t.Parallel()
 
 	// given
+	consul := New(Config{Tag: "marathon"})
 	app := &apps.App{
-		ID:     "/rootGroup/subGroup/subSubGroup/name",
-		Labels: map[string]string{"consul": "/some-other/name"},
+		ID: "someApp",
+		HealthChecks: []apps.HealthCheck{
+			{
+				Path:                   "/api/health",
+				Protocol:               "HTTP",
+				PortIndex:              0,
+				IntervalSeconds:        60,
+				TimeoutSeconds:         20,
+				MaxConsecutiveFailures: 3,
+			},
+		},
+		Labels: map[string]string{
+			"consul": "true",
+			"public": "tag",
+		},
 	}
-	consul := New(ConsulConfig{ConsulNameSeparator: "-"})
-
-	// when
-	serviceName := consul.ServiceName(app)
-
-	// then
-	assert.Equal(t, "some-other-name", serviceName)
-}
-
-func TestServiceName_WithInvalidLabelValue(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := &apps.App{
-		ID:     "/rootGroup/subGroup/subSubGroup/name",
-		Labels: map[string]string{"consul": "     ///"},
+	task := &apps.Task{
+		ID:    "someTask",
+		AppID: app.ID,
+		Host:  "invalid.localhost",
+		Ports: []int{8090, 8443},
 	}
-	consul := New(ConsulConfig{ConsulNameSeparator: "-"})
 
 	// when
-	serviceName := consul.ServiceName(app)
+	_, err := consul.marathonTaskToConsulServices(task, app)
 
 	// then
-	assert.Equal(t, "rootGroup-subGroup-subSubGroup-name", serviceName)
+	assert.Error(t, err)
 }
diff --git a/consul/consul_test_server.go b/consul/consul_test_server.go
index ad6d15e..a59c5de 100644
--- a/consul/consul_test_server.go
+++ b/consul/consul_test_server.go
@@ -10,8 +10,24 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func CreateTestServer(t *testing.T) *testutil.TestServer {
+	ports, err := getPorts(6)
+	assert.NoError(t, err)
+	return testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
+		c.Datacenter = fmt.Sprint("dc-", time.Now().UnixNano())
+		c.Ports = &testutil.TestPortConfig{
+			DNS:     ports[0],
+			HTTP:    ports[1],
+			RPC:     ports[2],
+			SerfLan: ports[3],
+			SerfWan: ports[4],
+			Server:  ports[5],
+		}
+	})
+}
+
 // Ask the kernel for free open ports that are ready to use
-func GetPorts(number int) ([]int, error) {
+func getPorts(number int) ([]int, error) {
 	ports := make([]int, number)
 	listener := make([]*net.TCPListener, number)
 	defer func() {
@@ -35,38 +51,22 @@ func GetPorts(number int) ([]int, error) {
 	return ports, nil
 }
 
-func CreateConsulTestServer(t *testing.T) *testutil.TestServer {
-	ports, err := GetPorts(6)
-	assert.NoError(t, err)
-	return testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
-		c.Datacenter = fmt.Sprint("dc-", time.Now().UnixNano())
-		c.Ports = &testutil.TestPortConfig{
-			DNS:     ports[0],
-			HTTP:    ports[1],
-			RPC:     ports[2],
-			SerfLan: ports[3],
-			SerfWan: ports[4],
-			Server:  ports[5],
-		}
-	})
-}
-
-func ConsulClientAtServer(server *testutil.TestServer) *Consul {
+func ClientAtServer(server *testutil.TestServer) *Consul {
 	return consulClientAtAddress(server.Config.Bind, server.Config.Ports.HTTP)
 }
 
-func FailingConsulClient() *Consul {
+func FailingClient() *Consul {
 	return consulClientAtAddress("127.5.5.5", 5555)
 }
 
 func consulClientAtAddress(host string, port int) *Consul {
-	config := ConsulConfig{
+	config := Config{
 		Timeout:             10 * time.Millisecond,
 		Port:                fmt.Sprintf("%d", port),
 		ConsulNameSeparator: ".",
 	}
 	consul := New(config)
 	// initialize the agents cache with a single client pointing at provided location
-	consul.GetAgent(host)
+	consul.AddAgent(host)
 	return consul
 }
diff --git a/events/deployment_info_test.go b/events/deployment_info_test.go
deleted file mode 100644
index e858267..0000000
--- a/events/deployment_info_test.go
+++ /dev/null
@@ -1,393 +0,0 @@
-package events
-
-import (
-	"io/ioutil"
-	"testing"
-
-	"github.com/allegro/marathon-consul/apps"
-	"github.com/stretchr/testify/assert"
-)
-
-func TestParseDeploymentInfo(t *testing.T) {
-	t.Parallel()
-	// given
-	blob, _ := ioutil.ReadFile("deployment_info.json")
-
-	// when
-	deploymentInfo, err := ParseDeploymentEvent(blob)
-	action := deploymentInfo.actions()[0]
-	app := deploymentInfo.originalDeployments().apps()[0]
-
-	// then
-	assert.NoError(t, err)
-	assert.Equal(t, "StopApplication", action.Type)
-	assert.Equal(t, "/internalName", action.AppId.String())
-	assert.Equal(t, "/internalName", app.ID.String())
-	assert.Equal(t, "consulName", app.Labels["consul"])
-}
-
-func TestParseDeploymentStepSuccess(t *testing.T) {
-	t.Parallel()
-	// given
-	blob, _ := ioutil.ReadFile("deployment_step_success.json")
-
-	// when
-	deploymentInfo, err := ParseDeploymentEvent(blob)
-	action := deploymentInfo.actions()[0]
-	app := deploymentInfo.RestartedConsulApps()[0]
-
-	// then
-	assert.NoError(t, err)
-	assert.Equal(t, "RestartApplication", action.Type)
-	assert.Equal(t, "/a", action.AppId.String())
-	assert.Equal(t, "/a", app.ID.String())
-	assert.Equal(t, "b", app.Labels["consul"])
-}
-
-func TestParseDeploymentStepSuccessWithGroups(t *testing.T) {
-	t.Parallel()
-	// given
-	blob, _ := ioutil.ReadFile("deployment_step_success_with_groups.json")
-
-	// when
-	deploymentInfo, err := ParseDeploymentEvent(blob)
-	action := deploymentInfo.actions()[0]
-	app := deploymentInfo.RestartedConsulApps()[0]
-
-	// then
-	assert.NoError(t, err)
-	assert.Equal(t, "RestartApplication", action.Type)
-	assert.Equal(t, "/com.example/things/something", action.AppId.String())
-	assert.Equal(t, "/com.example/things/something", app.ID.String())
-	assert.Equal(t, "something", app.Labels["consul"])
-}
-
-func TestOriginalDeployments_OnEmpty(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{}
-
-	// when
-	deployments := deploymentInfo.originalDeployments()
-
-	// then
-	assert.NotNil(t, deployments)
-}
-
-func TestTargetDeployments_OnEmpty(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{}
-
-	// when
-	deployments := deploymentInfo.targetDeployments()
-
-	// then
-	assert.NotNil(t, deployments)
-}
-
-func TestActions_OnEmpty(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{}
-
-	// when
-	actions := deploymentInfo.actions()
-
-	// then
-	assert.Len(t, actions, 0)
-}
-
-func TestStoppedConsulApps(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-					{ID: "app2", Labels: map[string]string{"consul": ""}},
-					{ID: "app3", Labels: map[string]string{"consul": ""}},
-					{ID: "app4"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "StartApplication", AppId: "app1"},
-				{Type: "StopApplication", AppId: "app2"},
-				{Type: "StopApplication", AppId: "app3"},
-				{Type: "StopApplication", AppId: "app4"},
-			},
-		},
-	}
-
-	// when
-	stoppedApps := deploymentInfo.StoppedConsulApps()
-
-	// then
-	assert.Len(t, stoppedApps, 2)
-	assert.Contains(t, stoppedApps, deploymentInfo.originalDeployments().apps()[1])
-	assert.Contains(t, stoppedApps, deploymentInfo.originalDeployments().apps()[2])
-}
-
-func TestRestartedConsulApps(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-					{ID: "app2", Labels: map[string]string{"consul": ""}},
-					{ID: "app3", Labels: map[string]string{"consul": "someName"}},
-					{ID: "app4"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "StartApplication", AppId: "app1"},
-				{Type: "RestartApplication", AppId: "app2"},
-				{Type: "RestartApplication", AppId: "app3"},
-				{Type: "RestartApplication", AppId: "app4"},
-			},
-		},
-	}
-
-	// when
-	restartedApps := deploymentInfo.RestartedConsulApps()
-
-	// then
-	assert.Len(t, restartedApps, 2)
-	assert.Contains(t, restartedApps, deploymentInfo.originalDeployments().apps()[1])
-	assert.Contains(t, restartedApps, deploymentInfo.originalDeployments().apps()[2])
-}
-
-func TestStoppedConsulApps_NoStoppedApps(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "StartApplication", AppId: "app1"},
-			},
-		},
-	}
-
-	// when
-	stoppedApps := deploymentInfo.StoppedConsulApps()
-
-	// then
-	assert.Len(t, stoppedApps, 0)
-}
-
-func TestStoppedConsulApps_OnEmpty(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{}
-
-	// when
-	stoppedApps := deploymentInfo.StoppedConsulApps()
-
-	// then
-	assert.Len(t, stoppedApps, 0)
-}
-
-func TestRenamedConsulApps(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "same"}},
-					{ID: "app2"},
-					{ID: "app3", Labels: map[string]string{"consul": "customName"}},
-				},
-			},
-			Target: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "same"}},
-					{ID: "app2"},
-					{ID: "app3", Labels: map[string]string{"consul": "newCustomName"}},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app1"},
-				{Type: "StartApplication", AppId: "app2"},
-				{Type: "RestartApplication", AppId: "app3"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 1)
-	assert.Contains(t, renamedApps, deploymentInfo.originalDeployments().apps()[2])
-}
-
-func TestRenamedConsulApps_OnEmptyPlan(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app2"},
-				{Type: "StartApplication", AppId: "app1"},
-				{Type: "RestartApplication", AppId: "app3"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 0)
-}
-
-func TestRenamedConsulApps_OnConsulTrueCase(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-					{ID: "app2"},
-				},
-			},
-			Target: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "true"}},
-					{ID: "app2"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app1"},
-				{Type: "RestartApplication", AppId: "app2"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 0)
-}
-
-func TestRenamedConsulApps_OnCustomNameAdded(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": ""}},
-					{ID: "app2"},
-				},
-			},
-			Target: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "customName"}},
-					{ID: "app2"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app1"},
-				{Type: "StartApplication", AppId: "app2"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 1)
-	assert.Contains(t, renamedApps, deploymentInfo.originalDeployments().apps()[0])
-}
-
-func TestRenamedConsulApps_OnCustomNameAddedToNonConsulApp(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-					{ID: "app2"},
-				},
-			},
-			Target: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "customName"}},
-					{ID: "app2"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app1"},
-				{Type: "StartApplication", AppId: "app2"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 0)
-}
-
-func TestRenamedConsulApps_OnConsulLabelRemoved(t *testing.T) {
-	t.Parallel()
-	// given
-	deploymentInfo := &DeploymentEvent{
-		Plan: &Plan{
-			Original: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1", Labels: map[string]string{"consul": "customName"}},
-					{ID: "app2"},
-				},
-			},
-			Target: &Deployments{
-				Apps: []*apps.App{
-					{ID: "app1"},
-					{ID: "app2"},
-				},
-			},
-		},
-		CurrentStep: &CurrentStep{
-			Actions: []*Action{
-				{Type: "RestartApplication", AppId: "app1"},
-				{Type: "StartApplication", AppId: "app2"},
-			},
-		},
-	}
-
-	// when
-	renamedApps := deploymentInfo.RenamedConsulApps()
-
-	// then
-	assert.Len(t, renamedApps, 1)
-	assert.Contains(t, renamedApps, deploymentInfo.originalDeployments().apps()[0])
-}
diff --git a/events/events_test.go b/events/events_test.go
index 8c15776..7c43856 100644
--- a/events/events_test.go
+++ b/events/events_test.go
@@ -18,7 +18,7 @@ func TestEventTypeInvalid(t *testing.T) {
 	t.Parallel()
 
 	out, err := EventType([]byte(`{}`))
-	assert.Equal(t, err, ErrNoEvent)
+	assert.Error(t, err)
 	assert.Equal(t, out, "")
 }
 
diff --git a/service/service_test.go b/service/service_test.go
new file mode 100644
index 0000000..4e1310e
--- /dev/null
+++ b/service/service_test.go
@@ -0,0 +1,48 @@
+package service
+
+import (
+	"testing"
+
+	"github.com/allegro/marathon-consul/apps"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMarathonTaskTAg(t *testing.T) {
+	t.Parallel()
+	assert.Equal(t, "marathon-task:my-task", MarathonTaskTag(apps.TaskID("my-task")))
+}
+
+func TestServiceTaskId(t *testing.T) {
+	t.Parallel()
+	// given
+	service := Service{
+		ID:   "123",
+		Name: "abc",
+		Tags: []string{MarathonTaskTag("my-task")},
+		RegisteringAgentAddress: "localhost",
+	}
+
+	// when
+	id, err := service.TaskId()
+
+	// then
+	assert.Equal(t, apps.TaskID("my-task"), id)
+	assert.NoError(t, err)
+}
+
+func TestServiceTaskId_NoMarathonTaskTag(t *testing.T) {
+	t.Parallel()
+	// given
+	service := Service{
+		ID:   "123",
+		Name: "abc",
+		Tags: []string{},
+		RegisteringAgentAddress: "localhost",
+	}
+
+	// when
+	_, err := service.TaskId()
+
+	// then
+	assert.Error(t, err)
+}
diff --git a/sync/error_consul_stub_test.go b/sync/error_consul_stub_test.go
deleted file mode 100644
index d312b32..0000000
--- a/sync/error_consul_stub_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package sync
-
-import "fmt"
-
-import (
-	"github.com/allegro/marathon-consul/apps"
-	consulapi "github.com/hashicorp/consul/api"
-)
-
-type errorConsul struct {
-}
-
-func (c errorConsul) GetServices(name string) ([]*consulapi.CatalogService, error) {
-	return nil, fmt.Errorf("Error occured")
-}
-
-func (c errorConsul) GetAllServices() ([]*consulapi.CatalogService, error) {
-	return nil, fmt.Errorf("Error occured")
-}
-
-func (c errorConsul) Register(task *apps.Task, app *apps.App) error {
-	return fmt.Errorf("Error occured")
-}
-
-func (c errorConsul) Deregister(serviceId apps.TaskId, agent string) error {
-	return fmt.Errorf("Error occured")
-}
-
-func (c errorConsul) ServiceName(app *apps.App) string {
-	return ""
-}
-
-func (c errorConsul) GetAgent(agent string) (*consulapi.Client, error) {
-	return nil, fmt.Errorf("Error occured")
-}
diff --git a/sync/error_marathon_stub_test.go b/sync/error_marathon_stub_test.go
index 8705db0..0734223 100644
--- a/sync/error_marathon_stub_test.go
+++ b/sync/error_marathon_stub_test.go
@@ -1,7 +1,7 @@
 package sync
 
 import (
-	"fmt"
+	"errors"
 
 	"github.com/allegro/marathon-consul/apps"
 )
@@ -10,17 +10,17 @@ type errorMarathon struct {
 }
 
 func (m errorMarathon) ConsulApps() ([]*apps.App, error) {
-	return nil, fmt.Errorf("Error")
+	return nil, errors.New("Error")
 }
 
-func (m errorMarathon) App(id apps.AppId) (*apps.App, error) {
-	return nil, fmt.Errorf("Error")
+func (m errorMarathon) App(id apps.AppID) (*apps.App, error) {
+	return nil, errors.New("Error")
 }
 
-func (m errorMarathon) Tasks(appId apps.AppId) ([]*apps.Task, error) {
-	return nil, fmt.Errorf("Error")
+func (m errorMarathon) Tasks(appID apps.AppID) ([]*apps.Task, error) {
+	return nil, errors.New("Error")
 }
 
 func (m errorMarathon) Leader() (string, error) {
-	return "", fmt.Errorf("Error")
+	return "", errors.New("Error")
 }
diff --git a/sync/error_service_registry_stub_test.go b/sync/error_service_registry_stub_test.go
new file mode 100644
index 0000000..18bfcd9
--- /dev/null
+++ b/sync/error_service_registry_stub_test.go
@@ -0,0 +1,31 @@
+package sync
+
+import (
+	"errors"
+
+	"github.com/allegro/marathon-consul/apps"
+	"github.com/allegro/marathon-consul/service"
+)
+
+type errorServiceRegistry struct {
+}
+
+func (c errorServiceRegistry) GetServices(name string) ([]*service.Service, error) {
+	return nil, errors.New("Error occured")
+}
+
+func (c errorServiceRegistry) GetAllServices() ([]*service.Service, error) {
+	return nil, errors.New("Error occured")
+}
+
+func (c errorServiceRegistry) Register(task *apps.Task, app *apps.App) error {
+	return errors.New("Error occured")
+}
+
+func (c errorServiceRegistry) DeregisterByTask(taskID apps.TaskID) error {
+	return errors.New("Error occured")
+}
+
+func (c errorServiceRegistry) Deregister(toDeregister *service.Service) error {
+	return errors.New("Error occured")
+}
diff --git a/sync/sync_bench_test.go b/sync/sync_bench_test.go
index 5c52046..019f67b 100644
--- a/sync/sync_bench_test.go
+++ b/sync/sync_bench_test.go
@@ -6,8 +6,8 @@ import (
 
 	"github.com/allegro/marathon-consul/apps"
 	"github.com/allegro/marathon-consul/consul"
+	"github.com/allegro/marathon-consul/service"
 	. "github.com/allegro/marathon-consul/utils"
-	consulapi "github.com/hashicorp/consul/api"
 )
 
 func BenchmarkDeregisterConsulServicesThatAreNotInMarathonApps10x2(b *testing.B) {
@@ -39,8 +39,8 @@ func BenchmarkDeregisterConsulServicesThatAreNotInMarathonApps100x100(b *testing
 
 func bench(b *testing.B, appsCount, instancesCount int) {
 	apps := marathonApps(appsCount, instancesCount)
-	instances := consulInstances(appsCount, instancesCount)
-	sync := New(Config{}, nil, consul.NewConsulStub())
+	instances := instances(appsCount, instancesCount)
+	sync := New(Config{}, nil, consul.NewConsulStub(), noopSyncStartedListener)
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
@@ -56,20 +56,18 @@ func marathonApps(appsCount, instancesCount int) []*apps.App {
 	return marathonApps
 }
 
-func consulInstances(appsCount, instancesCount int) []*consulapi.CatalogService {
-	consulServices := make([]*consulapi.CatalogService, appsCount*instancesCount)
+func instances(appsCount, instancesCount int) []*service.Service {
+	createdInstances := make([]*service.Service, appsCount*instancesCount)
 	for i := 0; i < appsCount*instancesCount; i++ {
 		app := ConsulApp(fmt.Sprintf("consul/service/no_%d", i), instancesCount)
 		for _, task := range app.Tasks {
-			consulServices[i] = &consulapi.CatalogService{
-				Address:        task.Host,
-				ServiceAddress: task.Host,
-				ServicePort:    task.Ports[0],
-				ServiceTags:    []string{"marathon"},
-				ServiceID:      task.ID.String(),
-				ServiceName:    app.ID.String(),
+			createdInstances[i] = &service.Service{
+				ID:   service.ServiceId(task.ID.String()),
+				Name: app.ID.String(),
+				Tags: []string{"marathon"},
+				RegisteringAgentAddress: task.Host,
 			}
 		}
 	}
-	return consulServices
+	return createdInstances
 }
diff --git a/sync/sync_test.go b/sync/sync_test.go
index a7ed8b9..11c87b3 100644
--- a/sync/sync_test.go
+++ b/sync/sync_test.go
@@ -7,14 +7,17 @@ import (
 	"testing"
 	"time"
 
-	"github.com/allegro/marathon-consul/apps"
 	"github.com/allegro/marathon-consul/consul"
 	"github.com/allegro/marathon-consul/marathon"
 	. "github.com/allegro/marathon-consul/utils"
-	consulapi "github.com/hashicorp/consul/api"
 	"github.com/stretchr/testify/assert"
+
+	"github.com/allegro/marathon-consul/apps"
+	"github.com/allegro/marathon-consul/service"
 )
 
+var noopSyncStartedListener = func(apps []*apps.App) {}
+
 func TestSyncJob_ShouldSyncOnLeadership(t *testing.T) {
 	t.Parallel()
 	// given
@@ -25,17 +28,15 @@ func TestSyncJob_ShouldSyncOnLeadership(t *testing.T) {
 		Enabled:  true,
 		Interval: 10 * time.Millisecond,
 		Leader:   "current.leader:8080",
-	}, marathon, services)
+	}, marathon, services, noopSyncStartedListener)
 
 	// when
 	ticker := sync.StartSyncServicesJob()
 
 	// then
+	<-time.After(15 * time.Millisecond)
 	ticker.Stop()
-	select {
-	case <-time.After(15 * time.Millisecond):
-		assert.Equal(t, 1, services.RegistrationsCount(app.Tasks[0].ID.String()))
-	}
+	assert.Equal(t, 2, services.RegistrationsCount(app.Tasks[0].ID.String()))
 }
 
 func TestSyncJob_ShouldNotSyncWhenDisabled(t *testing.T) {
@@ -48,13 +49,14 @@ func TestSyncJob_ShouldNotSyncWhenDisabled(t *testing.T) {
 		Enabled:  false,
 		Interval: 10 * time.Millisecond,
 		Leader:   "current.leader:8080",
-	}, marathon, services)
+	}, marathon, services, noopSyncStartedListener)
 
 	// when
 	ticker := sync.StartSyncServicesJob()
 
 	// then
 	assert.Nil(t, ticker)
+	<-time.After(15 * time.Millisecond)
 	assert.Equal(t, 0, services.RegistrationsCount(app.Tasks[0].ID.String()))
 }
 
@@ -68,17 +70,15 @@ func TestSyncJob_ShouldDefaultLeaderConfigurationToResolvedHostname(t *testing.T
 	sync := New(Config{
 		Enabled:  true,
 		Interval: 10 * time.Millisecond,
-	}, marathon, services)
+	}, marathon, services, noopSyncStartedListener)
 
 	// when
 	ticker := sync.StartSyncServicesJob()
 
 	// then
+	<-time.After(15 * time.Millisecond)
 	ticker.Stop()
-	select {
-	case <-time.After(15 * time.Millisecond):
-		assert.Equal(t, 1, services.RegistrationsCount(app.Tasks[0].ID.String()))
-	}
+	assert.Equal(t, 2, services.RegistrationsCount(app.Tasks[0].ID.String()))
 }
 
 func TestSyncServices_ShouldNotSyncOnNoForceNorLeaderSpecified(t *testing.T) {
@@ -87,7 +87,7 @@ func TestSyncServices_ShouldNotSyncOnNoForceNorLeaderSpecified(t *testing.T) {
 	app := ConsulApp("app1", 1)
 	marathon := marathon.MarathonerStubWithLeaderForApps("localhost:8080", app)
 	services := newConsulServicesMock()
-	sync := New(Config{}, marathon, services)
+	sync := New(Config{}, marathon, services, noopSyncStartedListener)
 
 	// when
 	ticker := sync.StartSyncServicesJob()
@@ -103,7 +103,7 @@ func TestSyncServices_ShouldNotSyncOnNoLeadership(t *testing.T) {
 	app := ConsulApp("app1", 1)
 	marathon := marathon.MarathonerStubWithLeaderForApps("leader:8080", app)
 	services := newConsulServicesMock()
-	sync := New(Config{Leader: "different.node:8090"}, marathon, services)
+	sync := New(Config{Leader: "different.node:8090"}, marathon, services, noopSyncStartedListener)
 
 	// when
 	err := sync.SyncServices()
@@ -119,7 +119,7 @@ func TestSyncServices_ShouldSyncOnForceWithoutLeadership(t *testing.T) {
 	app := ConsulApp("app1", 1)
 	marathon := marathon.MarathonerStubWithLeaderForApps("leader:8080", app)
 	services := newConsulServicesMock()
-	sync := New(Config{Leader: "different.node:8090", Force: true}, marathon, services)
+	sync := New(Config{Leader: "different.node:8090", Force: true}, marathon, services, noopSyncStartedListener)
 
 	// when
 	err := sync.SyncServices()
@@ -140,11 +140,11 @@ func newConsulServicesMock() *ConsulServicesMock {
 	}
 }
 
-func (c *ConsulServicesMock) GetServices(name string) ([]*consulapi.CatalogService, error) {
+func (c *ConsulServicesMock) GetServices(name string) ([]*service.Service, error) {
 	return nil, nil
 }
 
-func (c *ConsulServicesMock) GetAllServices() ([]*consulapi.CatalogService, error) {
+func (c *ConsulServicesMock) GetAllServices() ([]*service.Service, error) {
 	return nil, nil
 }
 
@@ -155,22 +155,18 @@ func (c *ConsulServicesMock) Register(task *apps.Task, app *apps.App) error {
 	return nil
 }
 
-func (c *ConsulServicesMock) RegistrationsCount(instanceId string) int {
+func (c *ConsulServicesMock) RegistrationsCount(instanceID string) int {
 	c.RLock()
 	defer c.RUnlock()
-	return c.registrations[instanceId]
-}
-
-func (c *ConsulServicesMock) ServiceName(app *apps.App) string {
-	return ""
+	return c.registrations[instanceID]
 }
 
-func (c *ConsulServicesMock) Deregister(serviceId apps.TaskId, agent string) error {
+func (c *ConsulServicesMock) DeregisterByTask(taskID apps.TaskID) error {
 	return nil
 }
 
-func (c *ConsulServicesMock) GetAgent(agentAddress string) (*consulapi.Client, error) {
-	return nil, nil
+func (c *ConsulServicesMock) Deregister(toDeregister *service.Service) error {
+	return nil
 }
 
 func TestSyncAppsFromMarathonToConsul(t *testing.T) {
@@ -192,7 +188,7 @@ func TestSyncAppsFromMarathonToConsul(t *testing.T) {
 	services, _ := consul.GetAllServices()
 	assert.Equal(t, 3, len(services))
 	for _, s := range services {
-		assert.NotEqual(t, "app3", s.ServiceName)
+		assert.NotEqual(t, "app3", s.Name)
 	}
 }
 
@@ -212,7 +208,7 @@ func TestSyncAppsFromMarathonToConsul_CustomServiceName(t *testing.T) {
 	// then
 	services, _ := consul.GetAllServices()
 	assert.Equal(t, 3, len(services))
-	assert.Equal(t, "customName", services[0].ServiceName)
+	assert.Equal(t, "customName", services[0].Name)
 }
 
 func TestRemoveInvalidServicesFromConsul(t *testing.T) {
@@ -236,7 +232,7 @@ func TestRemoveInvalidServicesFromConsul(t *testing.T) {
 	// then
 	services, _ := consul.GetAllServices()
 	assert.Equal(t, 1, len(services))
-	assert.Equal(t, "app2", services[0].ServiceName)
+	assert.Equal(t, "app2", services[0].Name)
 }
 
 func TestRemoveInvalidServicesFromConsul_WithCustomServiceName(t *testing.T) {
@@ -271,7 +267,7 @@ func TestRemoveInvalidServicesFromConsul_WithCustomServiceName(t *testing.T) {
 
 	services, _ := consul.GetAllServices()
 	assert.Equal(t, 1, len(services))
-	assert.Equal(t, "app2", services[0].ServiceName)
+	assert.Equal(t, "app2", services[0].Name)
 }
 
 func TestSyncOnlyHealthyServices(t *testing.T) {
@@ -292,16 +288,32 @@ func TestSyncOnlyHealthyServices(t *testing.T) {
 	services, _ := consul.GetAllServices()
 	assert.Equal(t, 2, len(services))
 	for _, s := range services {
-		assert.NotEqual(t, "app3-all-unhealthy", s.ServiceName)
+		assert.NotEqual(t, "app3-all-unhealthy", s.Name)
 	}
 }
 
+func TestSync_DeregisterServicesWithoutMarathonTaskTag(t *testing.T) {
+	t.Parallel()
+	// given
+	app := ConsulApp("app1", 1)
+	consul := consul.NewConsulStub()
+	consul.RegisterWithoutMarathonTaskTag(&app.Tasks[0], app)
+	marathonSync := newSyncWithDefaultConfig(marathon.MarathonerStubForApps(), consul)
+
+	// when
+	marathonSync.SyncServices()
+
+	// then
+	services, _ := consul.GetAllServices()
+	assert.Empty(t, services)
+}
+
 func TestSync_WithRegisteringProblems(t *testing.T) {
 	t.Parallel()
 	// given
 	marathon := marathon.MarathonerStubForApps(ConsulApp("/test/app", 3))
 	consul := consul.NewConsulStub()
-	consul.ErrorServices["test_app.1"] = fmt.Errorf("Problem on registration")
+	consul.FailRegisterForID("test_app.1")
 	sync := newSyncWithDefaultConfig(marathon, consul)
 	// when
 	err := sync.SyncServices()
@@ -311,6 +323,58 @@ func TestSync_WithRegisteringProblems(t *testing.T) {
 	assert.Len(t, services, 2)
 }
 
+func TestSync_ShouldRegisterMissingRegistrationInMultiregistrationScenario(t *testing.T) {
+	t.Parallel()
+	// given
+	app := ConsulAppMultipleRegistrations("/test/app", 1, 2)
+	marathon := marathon.MarathonerStubForApps(app)
+	consul := consul.NewConsulStub()
+
+	consul.RegisterOnlyFirstRegistrationIntent(&app.Tasks[0], app)
+	services, _ := consul.GetAllServices()
+	assert.Len(t, services, 1)
+
+	sync := newSyncWithDefaultConfig(marathon, consul)
+
+	// when
+	err := sync.SyncServices()
+
+	// then
+	services, _ = consul.GetAllServices()
+	assert.NoError(t, err)
+	assert.Len(t, services, 2)
+}
+
+/*
+This may happen if an application configuration is changed, but there are still tasks running the older one, e.g.
+the new deployment is still in progress or was cancelled. There's no way to access the original configuration
+that was used to start the currently running tasks. In such case, it's possible that a given task has more registrations
+than it's now expected from the new application configuration. In order to be safe we don't want to deregister anything,
+let someone make the deployment explicitly.
+*/
+func TestSync_SkipServiceHavingMoreRegistrationsThanExpectedInMultiregistrationScenario(t *testing.T) {
+	t.Parallel()
+	// given
+	app := ConsulAppMultipleRegistrations("/test/app", 1, 2)
+	marathon := marathon.MarathonerStubForApps(app)
+	consul := consul.NewConsulStub()
+
+	consul.Register(&app.Tasks[0], app)
+	services, _ := consul.GetAllServices()
+	assert.Len(t, services, 2)
+
+	sync := newSyncWithDefaultConfig(marathon, consul)
+
+	// when
+	app.PortDefinitions[1].Labels = map[string]string{} // make it a single-registration app
+	err := sync.SyncServices()
+
+	// then
+	services, _ = consul.GetAllServices()
+	assert.NoError(t, err)
+	assert.Len(t, services, 2)
+}
+
 func TestSync_WithDeregisteringProblems(t *testing.T) {
 	t.Parallel()
 	// given
@@ -319,8 +383,12 @@ func TestSync_WithDeregisteringProblems(t *testing.T) {
 	notMarathonApp := ConsulApp("/not/marathon", 1)
 	for _, task := range notMarathonApp.Tasks {
 		consulStub.Register(&task, notMarathonApp)
-		consulStub.ErrorServices[task.ID] = fmt.Errorf("Problem on deregistration")
 	}
+	allServices, _ := consulStub.GetAllServices()
+	for _, s := range allServices {
+		consulStub.FailDeregisterForID(s.ID)
+	}
+
 	sync := newSyncWithDefaultConfig(marathon, consulStub)
 	// when
 	err := sync.SyncServices()
@@ -345,25 +413,25 @@ func TestSync_WithConsulProblems(t *testing.T) {
 	t.Parallel()
 	// given
 	marathon := marathon.MarathonerStubForApps(ConsulApp("/test/app", 3))
-	consul := errorConsul{}
-	sync := newSyncWithDefaultConfig(marathon, consul)
+	serviceRegistry := errorServiceRegistry{}
+	sync := newSyncWithDefaultConfig(marathon, serviceRegistry)
 	// when
 	err := sync.SyncServices()
 	// then
 	assert.Error(t, err)
 }
 
-func newSyncWithDefaultConfig(marathon marathon.Marathoner, service consul.ConsulServices) *Sync {
-	return New(Config{Enabled: true, Leader: "localhost:8080"}, marathon, service)
+func newSyncWithDefaultConfig(marathon marathon.Marathoner, serviceRegistry service.ServiceRegistry) *Sync {
+	return New(Config{Enabled: true, Leader: "localhost:8080"}, marathon, serviceRegistry, noopSyncStartedListener)
 }
 
 func TestSync_AddingAgentsFromMarathonTasks(t *testing.T) {
 	t.Parallel()
 
-	consulServer := consul.CreateConsulTestServer(t)
+	consulServer := consul.CreateTestServer(t)
 	defer consulServer.Stop()
 
-	consulServices := consul.New(consul.ConsulConfig{
+	consulInstance := consul.New(consul.Config{
 		Port: fmt.Sprintf("%d", consulServer.Config.Ports.HTTP),
 		Tag:  "marathon",
 	})
@@ -371,7 +439,7 @@ func TestSync_AddingAgentsFromMarathonTasks(t *testing.T) {
 	app.Tasks[0].Host = consulServer.Config.Bind
 	app.Tasks[1].Host = consulServer.Config.Bind
 	marathon := marathon.MarathonerStubWithLeaderForApps("localhost:8080", app)
-	sync := New(Config{Leader: "localhost:8080"}, marathon, consulServices)
+	sync := New(Config{Leader: "localhost:8080"}, marathon, consulInstance, consulInstance.AddAgentsFromApps)
 
 	// when
 	err := sync.SyncServices()
@@ -380,7 +448,7 @@ func TestSync_AddingAgentsFromMarathonTasks(t *testing.T) {
 	assert.NoError(t, err)
 
 	// when
-	services, err := consulServices.GetAllServices()
+	services, err := consulInstance.GetAllServices()
 
 	// then
 	assert.NoError(t, err)
@@ -388,7 +456,7 @@ func TestSync_AddingAgentsFromMarathonTasks(t *testing.T) {
 
 	serviceNames := make(map[string]struct{})
 	for _, s := range services {
-		serviceNames[s.ServiceName] = struct{}{}
+		serviceNames[s.Name] = struct{}{}
 	}
 	assert.Len(t, serviceNames, 1)
 	assert.Contains(t, serviceNames, "serviceA")
diff --git a/utils/errors_test.go b/utils/errors_test.go
new file mode 100644
index 0000000..909571f
--- /dev/null
+++ b/utils/errors_test.go
@@ -0,0 +1,26 @@
+package utils
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestErrors_shouldMergeErrors(t *testing.T) {
+	t.Parallel()
+	// given
+	errs := []error{errors.New("first"), errors.New("second")}
+
+	// when
+	err := MergeErrorsOrNil(errs, "testing")
+
+	// then
+	assert.EqualError(t, err, "2 errors occured testing\n1: first\n2: second")
+}
+
+func TestErrors_shouldReturnNilForEmptyErrors(t *testing.T) {
+	t.Parallel()
+	// expect
+	assert.NoError(t, MergeErrorsOrNil([]error{}, "testing"))
+}
diff --git a/web/event_handler_test.go b/web/event_handler_test.go
index f2cae9e..97ab098 100644
--- a/web/event_handler_test.go
+++ b/web/event_handler_test.go
@@ -2,16 +2,13 @@ package web
 
 import (
 	"bytes"
-	"encoding/json"
-	"fmt"
+	"errors"
 	"net/http"
 	"net/http/httptest"
-	"strings"
 	"testing"
 
 	"github.com/allegro/marathon-consul/apps"
 	"github.com/allegro/marathon-consul/consul"
-	"github.com/allegro/marathon-consul/events"
 	"github.com/allegro/marathon-consul/marathon"
 	. "github.com/allegro/marathon-consul/utils"
 	"github.com/stretchr/testify/assert"
@@ -23,7 +20,7 @@ func TestWebHandler_NotHandleUnknownEventType(t *testing.T) {
 	// given
 	queue := make(chan event)
 	handler := newWebHandler(queue)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
+	stopChan := newEventHandler(0, nil, nil, queue).start()
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(`{"eventType":"test_event"}`)))
 
 	// when
@@ -112,9 +109,7 @@ func TestWebHandler_NotHandleInvalidEventType(t *testing.T) {
 
 	// then
 	assert.Equal(t, 400, recorder.Code)
-
-	assert.True(t, strings.HasPrefix(recorder.Body.String(), "json: cannot unmarshal array"))
-
+	assert.Equal(t, "json: cannot unmarshal array into Go value of type string\n", recorder.Body.String())
 }
 
 func TestWebHandler_HandleAppInvalidBody(t *testing.T) {
@@ -123,7 +118,7 @@ func TestWebHandler_HandleAppInvalidBody(t *testing.T) {
 	// given
 	queue := make(chan event)
 	handler := newWebHandler(queue)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
+	stopChan := newEventHandler(0, nil, nil, queue).start()
 	body := `{"type":  "app_terminated_event", "appID": 123}`
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
 
@@ -137,394 +132,13 @@ func TestWebHandler_HandleAppInvalidBody(t *testing.T) {
 	assert.Equal(t, "no event\n", recorder.Body.String())
 }
 
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationAction(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Empty(t, service.RegisteredServicesIds())
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_handleDeploymentInfoWithStopApplicationForOneApp(t *testing.T) {
-	t.Parallel()
-
-	// given
-	green := ConsulApp("/test/app.green", 3)
-	green.Labels[apps.MARATHON_CONSUL_LABEL] = "app"
-	blue := ConsulApp("/test/app.blue", 2)
-	blue.Labels[apps.MARATHON_CONSUL_LABEL] = "app"
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(green, blue)
-	assert.Len(t, service.RegisteredServicesIds(), 5)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(green))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 2)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionForMultipleApps(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app1 := ConsulApp("/test/app", 3)
-	app2 := ConsulApp("/test/otherapp", 2)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app1, app2)
-	assert.Len(t, service.RegisteredServicesIds(), 5)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app1, app2))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 0)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionForMultipleAppsAndProblemsDeregisteringOne(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app1 := ConsulApp("/test/app", 3)
-	app2 := ConsulApp("/test/otherapp", 2)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app1, app2)
-	service.ErrorServices["test_app.1"] = fmt.Errorf("Cannot deregister service")
-	assert.Len(t, service.RegisteredServicesIds(), 5)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app1, app2))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 1)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionForMultipleAppsAndProblemsGettingServicesForOne(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app1 := ConsulApp("/test/app", 3)
-	app2 := ConsulApp("/test/otherapp", 2)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app1, app2)
-	service.ErrorGetServices["test.app"] = fmt.Errorf("Something went terribly wrong!")
-	assert.Len(t, service.RegisteredServicesIds(), 5)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app1, app2))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionWithNoServicesRegistered(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := consul.NewConsulStub()
-	assert.Len(t, service.RegisteredServicesIds(), 0)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 0)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithInvalidBody(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(`{"eventType":"deployment_info", "Plan": 123}`)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionForNonConsulApp(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := NonConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionForCustomServiceName(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	app.Labels["consul"] = "someCustomServiceName"
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 0)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_NotHandleDeploymentInfoWithScaleApplicationAction(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	deploymentInfo := deploymentInfoWithStopApplicationActionForApps(app)
-	deploymentInfo.CurrentStep.Actions[0].Type = "ScaleApplication"
-	body, _ := json.Marshal(deploymentInfo)
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentInfoWithStopApplicationActionAndProblemsDeregistering(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	service.ErrorServices["test_app.1"] = fmt.Errorf("Cannot deregister service")
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentInfoWithStopApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 1)
-	assert.False(t, marathon.Interactions())
-}
-
-func deploymentInfoWithStopApplicationActionForApps(applications ...*apps.App) *events.DeploymentEvent {
-
-	deploymentInfo := &events.DeploymentEvent{
-		Type: "deployment_info",
-		Plan: &events.Plan{
-			Original: &events.Deployments{
-				Apps: []*apps.App{},
-			},
-		},
-		CurrentStep: &events.CurrentStep{
-			Actions: []*events.Action{},
-		},
-	}
-	for _, app := range applications {
-		deploymentInfo.Plan.Original.Apps = append(deploymentInfo.Plan.Original.Apps, app)
-		deploymentInfo.CurrentStep.Actions = append(deploymentInfo.CurrentStep.Actions, &events.Action{AppId: app.ID, Type: "StopApplication"})
-	}
-	return deploymentInfo
-}
-
-func TestWebHandler_HandleDeploymentStepSuccessWithRestartAndRenameApplicationAction(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentStepSuccessWithRestartAndRenameApplicationActionForApps(app))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 0)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentStepSuccessWithInvalidBody(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app := ConsulApp("/test/app", 3)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(`{"eventType":"deployment_step_success", "Plan": 123}`)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 3)
-	assert.False(t, marathon.Interactions())
-}
-
-func TestWebHandler_HandleDeploymentStepSuccessWithRestartApplicationActionForMultipleAppsAndProblemsDeregisteringOne(t *testing.T) {
-	t.Parallel()
-
-	// given
-	app1 := ConsulApp("/test/app", 3)
-	app2 := ConsulApp("/test/otherapp", 2)
-	marathon := marathon.MarathonerStubForApps()
-	service := newConsulStubWithApplicationsTasksRegistered(app1, app2)
-	service.ErrorServices["test_app.1"] = fmt.Errorf("Cannot deregister service")
-	assert.Len(t, service.RegisteredServicesIds(), 5)
-	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
-	body, _ := json.Marshal(deploymentStepSuccessWithRestartAndRenameApplicationActionForApps(app1, app2))
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handle(recorder, req)
-	stop()
-
-	// then
-	assertAccepted(t, recorder)
-	assert.Len(t, service.RegisteredServicesIds(), 1)
-	assert.False(t, marathon.Interactions())
-}
-
-func deploymentStepSuccessWithRestartAndRenameApplicationActionForApps(applications ...*apps.App) *events.DeploymentEvent {
-	deploymentInfo := &events.DeploymentEvent{
-		Type: "deployment_step_success",
-		Plan: &events.Plan{
-			Original: &events.Deployments{
-				Apps: []*apps.App{},
-			},
-			Target: &events.Deployments{
-				Apps: []*apps.App{},
-			},
-		},
-		CurrentStep: &events.CurrentStep{
-			Actions: []*events.Action{},
-		},
-	}
-	for _, app := range applications {
-		deploymentInfo.Plan.Original.Apps = append(deploymentInfo.Plan.Original.Apps, app)
-		targetApp := &apps.App{ID: app.ID, Labels: map[string]string{}}
-		if name, ok := app.Labels["consul"]; ok {
-			targetApp.Labels["consul"] = fmt.Sprintf("New%s", name)
-		}
-		deploymentInfo.Plan.Target.Apps = append(deploymentInfo.Plan.Target.Apps, targetApp)
-		deploymentInfo.CurrentStep.Actions = append(deploymentInfo.CurrentStep.Actions, &events.Action{AppId: app.ID, Type: "RestartApplication"})
-	}
-	return deploymentInfo
-}
-
 func TestWebHandler_NotHandleStatusEventWithInvalidBody(t *testing.T) {
 	t.Parallel()
 
 	// given
 	queue := make(chan event)
 	handler := newWebHandler(queue)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
+	stopChan := newEventHandler(0, nil, nil, queue).start()
 	body := `{
 	  "slaveId":"85e59460-a99e-4f16-b91f-145e0ea595bd-S0",
 	  "taskId":"python_simple.4a7e99d0-9cc1-11e5-b4d8-0a0027000004",
@@ -554,7 +168,7 @@ func TestWebHandler_NotHandleStatusEventAboutStartingTask(t *testing.T) {
 	// given
 	services := consul.NewConsulStub()
 	queue := make(chan event)
-	stopChan := newEventHandler(0, services, nil, queue).Start()
+	stopChan := newEventHandler(0, services, nil, queue).start()
 	handler := newWebHandler(queue)
 	ignoredTaskStatuses := []string{"TASK_STAGING", "TASK_STARTING", "TASK_RUNNING", "unknown"}
 	for _, taskStatus := range ignoredTaskStatuses {
@@ -582,7 +196,7 @@ func TestWebHandler_NotHandleStatusEventAboutStartingTask(t *testing.T) {
 		// then
 		assert.Equal(t, 202, recorder.Code)
 		assert.Equal(t, "OK\n", recorder.Body.String())
-		assert.Empty(t, services.RegisteredServicesIds())
+		assert.Empty(t, services.RegisteredTaskIDs())
 	}
 }
 
@@ -597,7 +211,7 @@ func TestWebHandler_HandleStatusEventAboutDeadTask(t *testing.T) {
 			service.Register(&task, app)
 		}
 		queue := make(chan event)
-		stopChan := newEventHandler(0, service, nil, queue).Start()
+		stopChan := newEventHandler(0, service, nil, queue).start()
 		handler := newWebHandler(queue)
 		body := `{
 		  "slaveId":"85e59460-a99e-4f16-b91f-145e0ea595bd-S0",
@@ -619,83 +233,16 @@ func TestWebHandler_HandleStatusEventAboutDeadTask(t *testing.T) {
 		recorder := httptest.NewRecorder()
 		handler.Handle(recorder, req)
 		stopChan <- stopEvent{}
-		servicesIds := service.RegisteredServicesIds()
+		taskIds := service.RegisteredTaskIDs()
 
 		// then
 		assert.Equal(t, 202, recorder.Code)
 		assert.Equal(t, "OK\n", recorder.Body.String())
-		assert.Len(t, servicesIds, 2)
-		assert.NotContains(t, servicesIds, app.Tasks[1].ID)
-		assert.Contains(t, servicesIds, app.Tasks[0].ID.String())
-		assert.Contains(t, servicesIds, app.Tasks[2].ID.String())
-	}
-}
-
-func TestWebHandler_HandleEventAboutUnhealthyKilledTask(t *testing.T) {
-	t.Parallel()
-	// given
-	app := ConsulApp("/test/app", 3)
-	service := consul.NewConsulStub()
-	for _, task := range app.Tasks {
-		service.Register(&task, app)
+		assert.Len(t, taskIds, 2)
+		assert.NotContains(t, taskIds, app.Tasks[1].ID)
+		assert.Contains(t, taskIds, app.Tasks[0].ID)
+		assert.Contains(t, taskIds, app.Tasks[2].ID)
 	}
-	queue := make(chan event)
-	stopChan := newEventHandler(0, service, nil, queue).Start()
-	handler := newWebHandler(queue)
-	body := `{
-	  "appId": "/test/app",
-	  "taskId": "` + app.Tasks[1].ID.String() + `",
-	  "version": "2016-03-16T13:05:00.590Z",
-	  "reason": "500 Internal Server Error",
-	  "host": "localhost",
-	  "slaveId": "4fb620fa-ba8d-4eb0-8ae3-f2912aaf015c-S0",
-	  "eventType": "unhealthy_task_kill_event",
-	  "timestamp": "2016-03-21T09:15:10.764Z"
-	}`
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handler.Handle(recorder, req)
-	stopChan <- stopEvent{}
-	servicesIds := service.RegisteredServicesIds()
-
-	// then
-	assert.Equal(t, 202, recorder.Code)
-	assert.Equal(t, "OK\n", recorder.Body.String())
-	assert.Len(t, servicesIds, 2)
-	assert.NotContains(t, servicesIds, app.Tasks[1].ID)
-	assert.Contains(t, servicesIds, app.Tasks[0].ID.String())
-	assert.Contains(t, servicesIds, app.Tasks[2].ID.String())
-}
-
-func TestWebHandler_NotHandleEventAboutUnhealthyKilledTaskWithInvalidBody(t *testing.T) {
-	t.Parallel()
-
-	// given
-	queue := make(chan event)
-	handler := newWebHandler(queue)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
-	body := `{
-	  "appId": "/test/app",
-	  "taskId": "test.app.1",
-	  "version": "2016-03-16T13:05:00.590Z",
-	  "reason": "500 Internal Server Error",
-	  "host": "localhost",
-	  "ports": 31372,
-	  "slaveId": "4fb620fa-ba8d-4eb0-8ae3-f2912aaf015c-S0",
-	  "eventType": "unhealthy_task_kill_event",
-	  "timestamp": "2016-03-21T09:15:10.764Z"
-	}`
-	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
-
-	// when
-	recorder := httptest.NewRecorder()
-	handler.Handle(recorder, req)
-	stopChan <- stopEvent{}
-
-	// then
-	assertAccepted(t, recorder)
 }
 
 func TestWebHandler_HandleStatusEventAboutDeadTaskErrOnDeregistration(t *testing.T) {
@@ -703,9 +250,9 @@ func TestWebHandler_HandleStatusEventAboutDeadTaskErrOnDeregistration(t *testing
 
 	// given
 	service := consul.NewConsulStub()
-	service.ErrorServices[apps.TaskId("task.1")] = fmt.Errorf("Cannot deregister task")
+	service.FailDeregisterByTaskForID("task.1")
 	queue := make(chan event)
-	stopChan := newEventHandler(0, service, nil, queue).Start()
+	stopChan := newEventHandler(0, service, nil, queue).start()
 	handler := newWebHandler(queue)
 	body := `{
 	  "slaveId":"85e59460-a99e-4f16-b91f-145e0ea595bd-S0",
@@ -730,7 +277,7 @@ func TestWebHandler_HandleStatusEventAboutDeadTaskErrOnDeregistration(t *testing
 
 	// then
 	assertAccepted(t, recorder)
-	assert.Empty(t, service.RegisteredServicesIds())
+	assert.Empty(t, service.RegisteredTaskIDs())
 }
 
 func TestWebHandler_NotHandleStatusEventAboutNonConsulAppsDeadTask(t *testing.T) {
@@ -740,7 +287,7 @@ func TestWebHandler_NotHandleStatusEventAboutNonConsulAppsDeadTask(t *testing.T)
 	app := NonConsulApp("/test/app", 3)
 	service := consul.NewConsulStub()
 	queue := make(chan event)
-	stopChan := newEventHandler(0, service, nil, queue).Start()
+	stopChan := newEventHandler(0, service, nil, queue).start()
 	handler := newWebHandler(queue)
 	taskStatuses := []string{"TASK_FINISHED", "TASK_FAILED", "TASK_KILLED", "TASK_LOST"}
 	for _, taskStatus := range taskStatuses {
@@ -778,7 +325,7 @@ func TestWebHandler_NotHandleHealthStatusEventWhenAppHasNotConsulLabel(t *testin
 	app := NonConsulApp("/test/app", 3)
 	marathon := marathon.MarathonerStubForApps(app)
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, marathon, queue).Start()
+	stopChan := newEventHandler(0, nil, marathon, queue).start()
 	handler := newWebHandler(queue)
 	body := healthStatusChangeEventForTask("test_app.1")
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
@@ -808,12 +355,12 @@ func TestWebHandler_HandleHealthStatusEvent(t *testing.T) {
 	recorder := httptest.NewRecorder()
 	handle(recorder, req)
 	stop()
-	servicesIds := service.RegisteredServicesIds()
+	taskIds := service.RegisteredTaskIDs()
 
 	// then
 	assertAccepted(t, recorder)
-	assert.Len(t, servicesIds, 1)
-	assert.Contains(t, servicesIds, app.Tasks[1].ID.String())
+	assert.Len(t, taskIds, 1)
+	assert.Contains(t, taskIds, app.Tasks[1].ID)
 	assert.True(t, marathon.Interactions())
 }
 
@@ -824,7 +371,7 @@ func TestWebHandler_HandleHealthStatusEventWithErrorsOnRegistration(t *testing.T
 	app := ConsulApp("/test/app", 3)
 	marathon := marathon.MarathonerStubForApps(app)
 	service := consul.NewConsulStub()
-	service.ErrorServices[app.Tasks[1].ID] = fmt.Errorf("Cannot register task")
+	service.FailRegisterForID(app.Tasks[1].ID)
 	handle, stop := NewHandler(Config{WorkersCount: 1}, marathon, service)
 	body := healthStatusChangeEventForTask("test_app.1")
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
@@ -836,7 +383,7 @@ func TestWebHandler_HandleHealthStatusEventWithErrorsOnRegistration(t *testing.T
 
 	// then
 	assertAccepted(t, recorder)
-	assert.Empty(t, service.RegisteredServicesIds())
+	assert.Empty(t, service.RegisteredTaskIDs())
 	assert.True(t, marathon.Interactions())
 }
 
@@ -848,7 +395,7 @@ func TestWebHandler_NotHandleHealthStatusEventForTaskWithNotAllHealthChecksPasse
 	app.Tasks[1].HealthCheckResults = []apps.HealthCheckResult{{Alive: true}, {Alive: false}}
 	marathon := marathon.MarathonerStubForApps(app)
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, marathon, queue).Start()
+	stopChan := newEventHandler(0, nil, marathon, queue).start()
 	handler := newWebHandler(queue)
 	body := healthStatusChangeEventForTask("test_app.1")
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
@@ -871,7 +418,7 @@ func TestWebHandler_NotHandleHealthStatusEventForTaskWithNoHealthCheck(t *testin
 	app.Tasks[0].HealthCheckResults = []apps.HealthCheckResult{}
 	marathon := marathon.MarathonerStubForApps(app)
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, marathon, queue).Start()
+	stopChan := newEventHandler(0, nil, marathon, queue).start()
 	handler := newWebHandler(queue)
 	body := healthStatusChangeEventForTask("/test/app.0")
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
@@ -891,7 +438,7 @@ func TestWebHandler_NotHandleHealthStatusEventWhenTaskIsNotAlive(t *testing.T) {
 
 	// given
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
+	stopChan := newEventHandler(0, nil, nil, queue).start()
 	handler := newWebHandler(queue)
 	body := `{
 	  "appId":"/test/app",
@@ -917,7 +464,7 @@ func TestWebHandler_NotHandleHealthStatusEventWhenBodyIsInvalid(t *testing.T) {
 
 	// given
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, nil, queue).Start()
+	stopChan := newEventHandler(0, nil, nil, queue).start()
 	handler := newWebHandler(queue)
 	body := `{
 	  "appId":"/test/app",
@@ -945,7 +492,7 @@ func TestWebHandler_HandleHealthStatusEventReturn202WhenMarathonReturnedError(t
 	app := ConsulApp("/test/app", 3)
 	marathon := marathon.MarathonerStubForApps(app)
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, marathon, queue).Start()
+	stopChan := newEventHandler(0, nil, marathon, queue).start()
 	handler := newWebHandler(queue)
 	body := `{
 	  "appId":"unknown",
@@ -974,7 +521,7 @@ func TestWebHandler_HandleHealthStatusEventWhenTaskIsNotInMarathon(t *testing.T)
 	app := ConsulApp("/test/app", 3)
 	marathon := marathon.MarathonerStubForApps(app)
 	queue := make(chan event)
-	stopChan := newEventHandler(0, nil, marathon, queue).Start()
+	stopChan := newEventHandler(0, nil, marathon, queue).start()
 	handler := newWebHandler(queue)
 	body := healthStatusChangeEventForTask("unknown.1")
 	req, _ := http.NewRequest("POST", "/events", bytes.NewBuffer([]byte(body)))
@@ -989,7 +536,7 @@ func TestWebHandler_HandleHealthStatusEventWhenTaskIsNotInMarathon(t *testing.T)
 	assert.True(t, marathon.Interactions())
 }
 
-func newConsulStubWithApplicationsTasksRegistered(applications ...*apps.App) *consul.ConsulStub {
+func newConsulStubWithApplicationsTasksRegistered(applications ...*apps.App) *consul.Stub {
 	service := consul.NewConsulStub()
 	for _, app := range applications {
 		for _, task := range app.Tasks {
@@ -1002,13 +549,13 @@ func newConsulStubWithApplicationsTasksRegistered(applications ...*apps.App) *co
 type BadReader struct{}
 
 func (r BadReader) Read(p []byte) (n int, err error) {
-	return 0, fmt.Errorf("Some error")
+	return 0, errors.New("Some error")
 }
 
-func healthStatusChangeEventForTask(taskId string) string {
+func healthStatusChangeEventForTask(taskID string) string {
 	return `{
 	  "appId":"/test/app",
-	  "taskId":"` + taskId + `",
+	  "taskId":"` + taskID + `",
 	  "version":"2015-12-07T09:02:48.981Z",
 	  "alive":true,
 	  "eventType":"health_status_changed_event",