Primeiramente vamos definir do que se trata o projeto de exemplo.
Resolvi postar o projeto no github para demonstrar o uso de alguns frameworks como: Autofac, Quartz e Topshelf. E suas facilidades.
Eu precisava monitorar o ip externo de um host, e não podia simplesmente instalar um programa de DDNS.
Pesquisei muito brevemente sobre algum serviço do windows que mantivesse o ip atualizado.
Como se trata de algo simples, fui direto ao ponto; Vou explicar a arquitetura (que está bem básica, na realidade) do projeto.

Você pode baixar o projeto em https://github.com/andrebtoe/NotifyChangeIp

A ideia é instalar um serviço do windows, e sempre que houver alteração do ip de internet onde o serviço se encontra instalado, será enviado um e-mail notificando.

Deixei parametrizado para implementar através da factories outras formas de processamento, após a constatação que o ip mudou.
Não vou explicar os detalhes de cada linha, mas a ideia do projeto que está no github.

Testando o projeto

Para quem quiser instalar o serviço do windows, baixe o release do projeto https://github.com/andrebtoe/NotifyChangeIp/blob/master/Release.zip descompacte; configure o arquivo ‘NotifyChangeIp.exe.config’; Abra a pasta no cmd do windows, como administrador e execute o comando

'NotifyChangeIp.exe install start'

Isto será o suficiente para instalar e iniciar o serviço em seu host.

Para remover basta executar

'NotifyChangeIp.exe uninstall'.

Mantendo consistência do serviço

Vamos ao ‘Topshelf’. A ideia deste é facilitar no processo que criação de um serviço windows. Ele facilita no momento de realizar debug e instalar, já que basta executar um comando para instalar e um para remover.
Uma função que acho interessante nele também, é a condição facilitada que ele disponibiliza para “esperar enquanto a task ainda não acabou”, utilizando ‘CancellationTokenSource’.

Configuração inicial do serviço

Na classe Program, definimos a configuração a classe que será responsável por gerenciar o serviço, que no
caso é a Scheduler, e quais métodos são invocados ao iniciar e parar o serviço. Também definimos o nome do serviço e descrição, ao quais são parametrizados, via classe Settings, esta última tem por responsabilidade gerenciar de forma tipada as chaves providas do App.config. Assim é evitados erros como chaves erradas e afins.

Parametrização

Como você pode ver no App.config, alguns parâmetros são definidos para o serviço e outros para envio de e-mail. Os dados de envio são obrigatórios apenas se a chave ‘type’ estiver como: ‘MailNotifyIp’ (no momento é a única opção, mas você pode adicionar uma outra forma de notificação, seja armazenar no banco, gerar um arquivo, etc). O valor de ‘type’ é definido ActionsNotifyIpFactory.cs.
Como veremos adiante, para implementar um ‘type’, basta incluir um item neste enum, implementar a interface e registrar na injeção de dependência.

Quartz

Na classe Scheduler podemos ver que no método ‘Start’, definimos uma instancia de ‘CancellationTokenSource’, para utilizarmos posteriormente no método ‘Stop’, e registramos via injeção de dependência na linha 20, para que seja possível recuperar no método ‘Stop’.
O registro de injeção de dependência é feito na classe DependencyInjection.cs como ‘SingleInstance’, afinal iremos precisar da mesma instancia que foi iniciada, no método ‘Stop’, e não iremos ter o mesmo contexto neste momento, por isso se faz necessário. Neste mesma classe também registramos os módulos do ‘Quartz’, este sera responsável pela recorrência da execução do serviço, é através dele que iremos definir se o processo deve ser repetido, qual intervalo, se deve executar de forma concorrente.

Repositório

Também registramos um repositório NotifyIpRepository.cs para consultar o ip externo do host através da API https://www.ipify.org e também para armazenar o ultimo ip, já que não se deve enviar e-mail a cada execução. Para isso armazenamos no registro do windows, já que se trata de uma informação de tamanho muito reduzido, como pode ser visto na imagem abaixo

Injeção de dependencia

E por último registramos o uso da factory na na linha 24 da classe DependencyInjection.cs.
Perceba que fazemos uso do enum ActionsNotifyIpFactory.cs. De uma interface
IProcessNotifyIp.cs. Que define que haverá um método async com um parâmetro de ‘IPAddress’ apenas. E sua implementação MailNotifyIpFactory.cs, que implementa a interface e se preocupa apenas em enviar por e-mail o ip recebido.
Como podemos perceber, este último parágrafo define como podemos incluir uma nova implementação da factory para saber o que fazer quando o ip mudou.

Jobs

Voltando para classe Scheduler.cs, temos o método ‘GetConfigureJobs’, este é responsável pode obter os jobs para ‘Quartz’, este trabalham de forma independente. Vamos supor que você queira verificar o espaço em disco do host, não faria sentido implementar no mesmo job que trabalha com notificação de ip por parâmetro, neste caso, você poderia adicionar outro job para fazer as devidas verificações.
Repare que nas definições de job definimos que o job deve repetir para sempre ‘RepeatForever()’ a cada x segundos ‘WithIntervalInSeconds()’, a qual é informado via App.config na chave ‘timeService’ no formato timespan.

Job concorrente

O que também definimos é a classe responsável por executar o job, neste caso NotifyIpJob.cs.
Definimos para o job que o mesmo não deve ser executado de forma concorrente através da notação ‘DisallowConcurrentExecution’, assim evitamos problemas, caso algum ou alguns métodos do repositório demore para responder, e comece outra execução do job.
No método ‘Execute’, invocamos o método ‘IpInternetHasChangedAsync’, para verificar se o ip mudou desde de a última consulta, basicamente ele verifica o que esta armazenado no registro do windows e o ip externo do host atual, caso seja diferente, um novo ip sera armazenado e o tipo de ‘IProcessNotifyIp’ sera executado.

Factory

No construtor desta classe injetamos ‘IIndex<ActionsNotifyIpFactory, IProcessNotifyIp> iindexProcessNotifyIp’, ele servirá de base obter a implementação da factory, como é visto na lista 36.

Validação, inicio de fim do serviço

Voltando para classe Scheduler.cs, temos o método ‘ConfigureScheduler’ que registra a injeção de dependência de ‘IScheduler’, para obter no método ‘Stop’. Também adiciona os jobs e inicia a scheduler.
Também temos o método ‘VaidateParameters’, que invoca a validação dos parametros de App.config. Montei a validação de forma bem básica através de Specifications, veja pasta SpecificationsValidations. Eu prefiro trabalhar com https://fluentvalidation.net/, porem não era o foco aqui e não queria me alongar já que se trata de validação de propriedades estáticas como pode ser visto Settings.

Espero ter ajudado!!
Até a próxima!