Estrangulamento de legado na prática, com código, criando um serviço de alta performance — Tiago Tartari

Tiago Tartari
7 min readFeb 3, 2021

A frase de Peter Drucker, “ Não há nada tão inútil quanto fazer eficientemente o que não deveria ser feito. “, embora uma verdade, na prática, empresas optam por construir sistemas em castelos de cartas.

Assim sendo, nesse artigo, trago, na prática, o que fiz para estrangular um componente de um sistema legado em um serviço de alta performance, resiliente e escalável.

Dessa forma, recomendo fortemente a leitura do post “ Como transformei um componente do legado em um serviço de alta performance, resiliente e escalável “.

Arquitetura adotada TO-BE

Em princípio, é importante deixar claro como deve ser a leitura desse post. A ordem do yaml está baseada na ordem dos componentes que vamos utilizar, ao final, deixarei o código completo do docker-compose para consulta.

Ressalto aqui, que o post não tem o propósito aprofundar-se nas ferramentas citadas, como por exemplo SQL Server, Debezium, Apache Kafka e Apache NiFi, mas sim na solução.

Sempre que possível indicarei leituras de posts para reforçar o que estou mostrando.

Habilitamos o CDC, Change Data Capture, no SQL do legado.

O CDC, que por padrão vem desabilitado no SQL Server, é utilizado para fazer capturas como, INSERT, UPDATE e DELETE. Na prática, quando habilitado em uma tabela, ele automaticamente cria uma outra tabela com schema diferente e somente o processo dele pode inserir, alterar ou excluir nessa nova tabela, à nós, nos cabe somente o SELECT.

Recomendo a leitura dos artigos da Microsoft e o do MVP Dirceu Resende sobre o Change Data Capture.

Em nosso caso, estamos habilitando o CDC para capturarmos todas as informações referente a movimentação de estoque, lembrando que, uma das restrições do projeto, mencionadas no primeiro post, é de não gerar impacto no legado, motivo pela qual decidimos habilitar o Change Data Capture.

Abaixo o exemplo para subir um SQL Server em container. Ponto de atenção é que para o CDC funcionar o Agent deve estar habilitado.

version: '3.4' services: mssql-server: image: "mcr.microsoft.com/mssql/server:2019-latest" environment: ACCEPT_EULA: "Y" MSSQL_SA_PASSWORD: "s3nhaEhPa55w0rD" MSSQL_COLLATION: "SQL_Latin1_General_CP1_CI_AS" MSSQL_AGENT_ENABLED: "True" ports: - "1433:1433" container_name: "mssql-server" volumes: - /c/docker/volumes/sqlserver/data:/var/opt/mssql/data - /c/docker/volumes/sqlserver/log:/var/opt/mssql/log - /c/docker/volumes/sqlserver/secrets:/var/opt/mssql/secrets restart: always networks: - inventory networks: inventory: driver: bridge

Script para criação do banco de dados do legado.

CREATE DATABASE Inventory GO USE Inventory GO CREATE TABLE [Warehouse] ( [ID]	INT NOT NULL ) GO ALTER TABLE [Warehouse] ADD CONSTRAINT [PK_Warehouse] PRIMARY KEY ( [ID] ) GO CREATE TABLE [Sku] ( [Code] CHAR(13) NOT NULL ) ALTER TABLE [Sku] ADD CONSTRAINT [PK_Sku] PRIMARY KEY ( [Code] ) GO CREATE TABLE [Inventory] ( [SkuCode] CHAR(13) NOT NULL, [WarehouseId]	INT NOT NULL, [Qty] INT NOT NULL ) GO ALTER TABLE [Inventory] ADD CONSTRAINT [PK_Inventory] PRIMARY KEY NONCLUSTERED ( [SkuCode], [WarehouseId] ) GO CREATE INDEX [IX_Inventory] ON [Inventory]([SkuCode]) GO ALTER TABLE [Inventory] ADD CONSTRAINT [FK_Inventory_Warehouse] FOREIGN KEY ([WarehouseId]) REFERENCES [Warehouse]([ID]) GO ALTER TABLE [Inventory] ADD CONSTRAINT [FK_Inventory_Sku] FOREIGN KEY ([SkuCode]) REFERENCES [Sku]([Code]) GO

Habilitando o CDC no SQL Server e na tabela Inventory.

EXEC sp_changedbowner 'sa' GO EXEC sys.sp_cdc_enable_db GO EXEC sys.sp_cdc_enable_table @source_schema = N'dbo', @source_name = N'Inventory', @role_name = NULL, @supports_net_changes = 1 GO SELECT name, is_tracked_by_cdc FROM sys.tables WHERE is_tracked_by_cdc = 1 EXECUTE sys.sp_cdc_help_change_data_capture @source_schema = 'dbo', @source_name = 'Inventory';

Como dito acima, o CDC, por padrão, cria um novo schema e uma nova tabela, em nosso caso cdc.dbo_Inventory_CT. Após habilita-lo, execute o script abaixo para inserirmos dados nas tabelas referente ao banco de dados do legado.

INSERT INTO [Warehouse](ID)VALUES(1) GO INSERT INTO Sku([Code]) VALUES('NOUN8X585Q-28'); GO INSERT INTO [Inventory]([SkuCode],[WarehouseId],[Qty])VALUES('NOUN8X585Q-28',1, 1000);

Configuramos o Debezium com Zookeeper, Kafka Connect e Apache Kafka

O Debezium é uma plataforma distribuída, open-source, que transforma um banco de dados em um fluxo de eventos, o que permite receber esses eventos real time. Construído sobre o Apache Kafka, o Debezium permite resiliência e escalabilidade, de tal forma que, o Apache Kafka torna-se uma poderosa e aliada ferramenta em cenários de grandes fluxos de informações.

Assim sendo, recomendo fortemente a leitura do blog do Debezium.

Abaixo o exemplo de como subir o Debezium com Zookeeper, Kafka Connect e o Apache Kafka.

NOTA: Importante destacar para que para resolver o problema essa stack foi útil, há cenários mais complexos e para isso você deve avaliar com muita cautela.

version: '3.4' services: zookeeper: image: "confluentinc/cp-zookeeper" environment: ZOOKEEPER_CLIENT_PORT: 2181 ports: - "2181:2181" container_name: "zookeeper" restart: always networks: - inventory kafka: image: "confluentinc/cp-kafka" environment: KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_ADVERTISED_HOST_NAME: "kafka" KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:9092" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_LOG_CLEANER_DELETE_RETENTION_MS: 5000 KAFKA_BROKER_ID: 1 KAFKA_MIN_INSYNC_REPLICAS: 1 ports: - "9092:9092" depends_on: - zookeeper container_name: "kafka" restart: always networks: - inventory connect: image: "debezium/connect" environment: GROUP_ID: 1 REST_ADVERTISED_HOST_NAME: "connect" BOOTSTRAP_SERVERS: "kafka:9092" ZOOKEEPER_CONNECT: "zookeeper:2181" CONFIG_STORAGE_TOPIC: "my_connect_configs" OFFSET_STORAGE_TOPIC: "my_connect_offsets" STATUS_STORAGE_TOPIC: "my_connect_statuses" ports: - "8083:8083" depends_on: - zookeeper - kafka container_name: "connect" networks: - inventory kafdrop: image: "obsidiandynamics/kafdrop" environment: KAFKA_BROKERCONNECT: kafka:9092 JVM_OPTS: -Xms32M -Xmx64M ports: - 9005:9000 depends_on: - kafka container_name: "kafdrop" networks: - inventory networks: inventory: driver: bridge

Logo após, é necessário registar a configuração do Connector para o SQL Server, você pode utilizar o POSTMAN fazendo um POST para a API do Kafka Connect http://localhost:8083/connectors.

Há uma documentação abrangente a cerca das APIs do Kafka Connect nesse link https://docs.confluent.io/platform/current/connect/references/restapi.html, da uma conferida lá.

{ "name": "sqlserverInventoryConnector", "config" : { "connector.class": "io.debezium.connector.sqlserver.SqlServerConnector", "tasks.max" : 1, "database.hostname": "mssql-server", "database.port": 1433, "database.user": "sa", "database.password": "s3nhaEhPa55w0rD", "database.dbname" : "Inventory", "database.server.name" : "mssql-server", "table.whitelist" : "dbo.Inventory", "database.history.kafka.bootstrap.servers" : "kafka:9092", "database.history.kafka.topic" : "dbhistory.Inventory", "decimal.handling.mode": "string" } }

Em seguida, após a execução via POSTMAN, teremos o resultado abaixo.

Há uma ferrament muito básica, mas que nos ajuda de maneira visual ver os tópicos criados pelo Kafka o nome dela é kafdrop. Abaixo está o exemplo de como utilizá-la.

version: '3.4' services: kafdrop: image: "obsidiandynamics/kafdrop" environment: KAFKA_BROKERCONNECT: kafka:9092 JVM_OPTS: -Xms32M -Xmx64M ports: - 9005:9000 depends_on: - kafka container_name: "kafdrop" networks: - inventory networks: inventory: driver: bridge

Após subí-la, teremos ela respondendo em http://localhost:9905.

Logo depois, ao clicar no tópico mssql-server.dbo.Inventory teremos alguns dados relevantes, como a possibilidade de visualizar as mensagens nesse tópico, os consumidores entre outras.

Subindo Redis, Mongo, Mongo Express e Apache NiFi

Visto que estamos avançando em nossa arquitetura, abaixo estou colocando o yaml que irá subir Redis, Mongo, Mongo Express e o Apache NiFi.

version: '3.4' services: nifi: image: apache/nifi environment: - NIFI_WEB_HTTP_PORT=8080 ports: - 8080:8080 container_name: nifi restart: always networks: - inventory mongodb: image: mongo environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: MongoDB2021! container_name: mongodb ports: - 27017:27017 volumes: - ./mongodb/data:/data/db restart: always networks: - inventory mongo-express: image: mongo-express ports: - 8081:8081 environment: ME_CONFIG_BASICAUTH_USERNAME: tiagotartari ME_CONFIG_BASICAUTH_PASSWORD: MongoExpress2021! ME_CONFIG_MONGODB_SERVER: mongodb ME_CONFIG_MONGODB_PORT: 27017 ME_CONFIG_MONGODB_ADMINUSERNAME: root ME_CONFIG_MONGODB_ADMINPASSWORD: MongoDB2021! container_name: mongodb-express links: - mongodb depends_on: - mongodb networks: - inventory redis: image: redis container_name: redis ports: - 6379:6379 volumes: - ./redis/data:/data networks: - inventory networks: inventory: driver: bridge

Entendendo o fluxo do Apache NiFi

Basicamente no Apache NiFi, teremos pouco a fazer. Configurei um processor que será o principal a ser estimulado pela comunicação com o Apache Kafka, passamos por uma transformação pegando somente o que precisamos, ou seja, a mudança no banco de dados, insiro no mongo e se der sucesso vou no redis para fazer um release na chave do cache que é o SKU.

Abaixo fica está o fluxo.

Conclusão

Por fim, não precisamos criar complexidade para de fato iniciar a migração do legado. Não é fácil, entretando possível.

Reparem também que, não foi adicionado riscos ao legado, pelo contrário, diminuimos a possibilidade de indisponibilidade e eliminamos o principal ponto de falha.

O docker-compose completo está logo abaixo.

version: '3.4' services: mssql-server: image: "mcr.microsoft.com/mssql/server:2019-latest" environment: ACCEPT_EULA: "Y" MSSQL_SA_PASSWORD: "s3nhaEhPa55w0rD" MSSQL_COLLATION: "SQL_Latin1_General_CP1_CI_AS" MSSQL_AGENT_ENABLED: "True" ports: - "1433:1433" container_name: "mssql-server" volumes: - /c/docker/volumes/sqlserver/data:/var/opt/mssql/data - /c/docker/volumes/sqlserver/log:/var/opt/mssql/log - /c/docker/volumes/sqlserver/secrets:/var/opt/mssql/secrets restart: always networks: - inventory zookeeper: image: "confluentinc/cp-zookeeper" environment: ZOOKEEPER_CLIENT_PORT: 2181 ports: - "2181:2181" container_name: "zookeeper" restart: always networks: - inventory kafka: image: "confluentinc/cp-kafka" environment: KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181" KAFKA_ADVERTISED_HOST_NAME: "kafka" KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:9092" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_LOG_CLEANER_DELETE_RETENTION_MS: 5000 KAFKA_BROKER_ID: 1 KAFKA_MIN_INSYNC_REPLICAS: 1 ports: - "9092:9092" depends_on: - zookeeper container_name: "kafka" restart: always networks: - inventory connect: image: "debezium/connect" environment: GROUP_ID: 1 REST_ADVERTISED_HOST_NAME: "connect" BOOTSTRAP_SERVERS: "kafka:9092" ZOOKEEPER_CONNECT: "zookeeper:2181" CONFIG_STORAGE_TOPIC: "my_connect_configs" OFFSET_STORAGE_TOPIC: "my_connect_offsets" STATUS_STORAGE_TOPIC: "my_connect_statuses" ports: - "8083:8083" depends_on: - zookeeper - kafka container_name: "connect" networks: - inventory kafdrop: image: "obsidiandynamics/kafdrop" environment: KAFKA_BROKERCONNECT: kafka:9092 JVM_OPTS: -Xms32M -Xmx64M ports: - 9005:9000 depends_on: - kafka container_name: "kafdrop" networks: - inventory nifi: image: apache/nifi environment: - NIFI_WEB_HTTP_PORT=8080 ports: - 8080:8080 container_name: nifi restart: always networks: - inventory mongodb: image: mongo environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: MongoDB2021! container_name: mongodb ports: - 27017:27017 volumes: - ./mongodb/data:/data/db restart: always networks: - inventory mongo-express: image: mongo-express ports: - 8081:8081 environment: ME_CONFIG_BASICAUTH_USERNAME: tiagotartari ME_CONFIG_BASICAUTH_PASSWORD: MongoExpress2021! ME_CONFIG_MONGODB_SERVER: mongodb ME_CONFIG_MONGODB_PORT: 27017 ME_CONFIG_MONGODB_ADMINUSERNAME: root ME_CONFIG_MONGODB_ADMINPASSWORD: MongoDB2021! container_name: mongodb-express links: - mongodb depends_on: - mongodb networks: - inventory redis: image: redis container_name: redis ports: - 6379:6379 volumes: - ./redis/data:/data networks: - inventory networks: inventory: driver: bridge

Originally published at https://www.tiagotartari.net on February 3, 2021.

--

--

Tiago Tartari

Microsoft MVP, programador por mais de 18 anos onde 10 deles atuando como arquiteto de soluções para e-commerce, palestrante técnico, apaixonado por performance