Clean up worker service (#272)

* Remove Java project from worker

* Update .NET Core and remove old dotnet codebase from worker service

* Remove Windows-based Compose files and info from README

* Update .NET to 7.0
This commit is contained in:
Michael Irwin
2022-12-16 17:27:32 -05:00
committed by GitHub
parent eaa19a7161
commit 7e7445a601
34 changed files with 48 additions and 948 deletions

View File

@@ -11,7 +11,7 @@ Download [Docker Desktop](https://www.docker.com/products/docker-desktop) for Ma
## Linux Containers
The Linux stack uses Python, Node.js, .NET Core (or optionally Java), with Redis for messaging and Postgres for storage.
The Linux stack uses Python, Node.js, .NET Core, with Redis for messaging and Postgres for storage.
> If you're using [Docker Desktop on Windows](https://store.docker.com/editions/community/docker-ce-desktop-windows), you can run the Linux version by [switching to Linux containers](https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers), or run the Windows containers version.
@@ -30,26 +30,6 @@ Once you have your swarm, in this directory run:
docker stack deploy --compose-file docker-stack.yml vote
```
## Windows Containers
An alternative version of the app uses Windows containers based on Nano Server. This stack runs on .NET Core, using [NATS](https://nats.io) for messaging and [TiDB](https://github.com/pingcap/tidb) for storage.
You can build from source using:
```
docker compose -f docker-compose-windows.yml build
```
Then run the app using:
```
docker compose -f docker-compose-windows.yml up -d
```
> Or in a Windows swarm, run `docker stack deploy -c docker-stack-windows.yml vote`
The app will be running at [http://localhost:5000](http://localhost:5000), and the results will be at [http://localhost:5001](http://localhost:5001).
Run the app in Kubernetes
-------------------------
@@ -85,7 +65,7 @@ Architecture
* A front-end web app in [Python](/vote) or [ASP.NET Core](/vote/dotnet) which lets you vote between two options
* A [Redis](https://hub.docker.com/_/redis/) or [NATS](https://hub.docker.com/_/nats/) queue which collects new votes
* A [.NET Core](/worker/src/Worker), [Java](/worker/src/main) or [.NET Core 2.1](/worker/dotnet) worker which consumes votes and stores them in…
* A [.NET Core](/worker/) worker which consumes votes and stores them in…
* A [Postgres](https://hub.docker.com/_/postgres/) or [TiDB](https://hub.docker.com/r/dockersamples/tidb/tags/) database backed by a Docker volume
* A [Node.js](/result) or [ASP.NET Core SignalR](/result/dotnet) webapp which shows the results of the voting in real time

View File

@@ -1,57 +0,0 @@
version: "3"
services:
vote:
build: ./vote
command: python app.py
volumes:
- ./vote:/app
ports:
- "5000:80"
networks:
- front-tier
- back-tier
result:
build: ./result
command: nodemon server.js
volumes:
- ./result:/app
ports:
- "5001:80"
- "5858:5858"
networks:
- front-tier
- back-tier
worker:
build:
context: ./worker
dockerfile: Dockerfile.j
networks:
- back-tier
redis:
image: redis:alpine
container_name: redis
ports: ["6379"]
networks:
- back-tier
db:
image: postgres:9.4
container_name: db
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
volumes:
- "db-data:/var/lib/postgresql/data"
networks:
- back-tier
volumes:
db-data:
networks:
front-tier:
back-tier:

View File

@@ -1,48 +0,0 @@
version: "3.2"
services:
vote:
image: dockersamples/examplevotingapp_vote:dotnet-nanoserver-1809
build:
context: ./vote/dotnet
dockerfile: Dockerfile.1809
ports:
- "5000:80"
depends_on:
- message-queue
result:
image: dockersamples/examplevotingapp_result:dotnet-nanoserver-1809
build:
context: ./result/dotnet
dockerfile: Dockerfile.1809
ports:
- "5001:80"
environment:
- "ConnectionStrings:ResultData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
depends_on:
- db
worker:
image: dockersamples/examplevotingapp_worker:dotnet-nanoserver-1809
build:
context: ./worker/dotnet
dockerfile: Dockerfile.1809
environment:
- "ConnectionStrings:VoteData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
depends_on:
- message-queue
- db
message-queue:
image: nats:2.0.4
db:
image: dockersamples/tidb:nanoserver-1809
ports:
- "3306:4000"
networks:
default:
external:
name: nat

View File

@@ -1,45 +0,0 @@
version: "3.2"
services:
vote:
image: dockersamples/examplevotingapp_vote:dotnet-nanoserver-sac2016
build:
context: ./vote/dotnet
ports:
- "5000:80"
depends_on:
- message-queue
result:
image: dockersamples/examplevotingapp_result:dotnet-nanoserver-sac2016
build:
context: ./result/dotnet
ports:
- "5001:80"
environment:
- "ConnectionStrings:ResultData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
depends_on:
- db
worker:
image: dockersamples/examplevotingapp_worker:dotnet-nanoserver-sac2016
build:
context: ./worker/dotnet
environment:
- "ConnectionStrings:VoteData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
depends_on:
- message-queue
- db
message-queue:
image: nats:nanoserver
db:
image: dockersamples/tidb:nanoserver
ports:
- "3306:4000"
networks:
default:
external:
name: nat

View File

@@ -1,49 +0,0 @@
version: "3.2"
services:
vote:
image: dockersamples/examplevotingapp_vote:dotnet-nanoserver-1809
ports:
- "5000:80"
deploy:
mode: replicated
replicas: 4
networks:
- frontend
- backend
result:
image: dockersamples/examplevotingapp_result:dotnet-nanoserver-1809
environment:
- "ConnectionStrings:ResultData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
ports:
- "5001:80"
networks:
- frontend
- backend
worker:
image: dockersamples/examplevotingapp_worker:dotnet-nanoserver-1809
environment:
- "ConnectionStrings:VoteData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
deploy:
mode: replicated
replicas: 3
networks:
- backend
message-queue:
image: dockersamples/nats:nanoserver-1809
networks:
- backend
db:
image: dockersamples/tidb:nanoserver-1809
ports:
- "3306:4000"
networks:
- backend
networks:
frontend:
backend:

View File

@@ -1,61 +0,0 @@
version: "3.2"
services:
vote:
image: dockersamples/examplevotingapp_vote:dotnet-nanoserver-sac2016
ports:
- mode: host
target: 80
published: 5000
deploy:
endpoint_mode: dnsrr
networks:
- frontend
- backend
result:
image: dockersamples/examplevotingapp_result:dotnet-nanoserver-sac2016
environment:
- "ConnectionStrings:ResultData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
ports:
- mode: host
target: 80
published: 5001
deploy:
endpoint_mode: dnsrr
networks:
- frontend
- backend
worker:
image: dockersamples/examplevotingapp_worker:dotnet-nanoserver-sac2016
environment:
- "ConnectionStrings:VoteData=Server=db;Port=4000;Database=votes;User=root;SslMode=None"
deploy:
endpoint_mode: dnsrr
mode: replicated
replicas: 3
networks:
- backend
message-queue:
image: nats:nanoserver
deploy:
endpoint_mode: dnsrr
networks:
- backend
db:
image: dockersamples/tidb:nanoserver
ports:
- mode: host
target: 4000
published: 3306
deploy:
endpoint_mode: dnsrr
networks:
- backend
networks:
frontend:
backend:

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>worker</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -1,2 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.apt.aptEnabled=false

View File

@@ -1,7 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.processAnnotations=disabled
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.7

View File

@@ -1,4 +0,0 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@@ -1,16 +1,32 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 as builder
# because of dotnet, we always build on amd64, and target platforms in cli
# dotnet doesn't support QEMU for building or running.
# (errors common in arm/v7 32bit) https://github.com/dotnet/dotnet-docker/issues/1537
# https://hub.docker.com/_/microsoft-dotnet
# hadolint ignore=DL3029
FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:7.0 as build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM"
WORKDIR /Worker
COPY src/Worker/Worker.csproj .
RUN dotnet restore
WORKDIR /source
COPY *.csproj .
RUN case ${TARGETPLATFORM} in \
"linux/amd64") ARCH=x64 ;; \
"linux/arm64") ARCH=arm64 ;; \
"linux/arm/v7") ARCH=arm ;; \
esac \
&& dotnet restore -r linux-${ARCH}
COPY src/Worker/ .
RUN dotnet publish -c Release -o /out Worker.csproj
COPY . .
RUN case ${TARGETPLATFORM} in \
"linux/amd64") ARCH=x64 ;; \
"linux/arm64") ARCH=arm64 ;; \
"linux/arm/v7") ARCH=arm ;; \
esac \
&& dotnet publish -c release -o /app -r linux-${ARCH} --self-contained false --no-restore
# app image
FROM mcr.microsoft.com/dotnet/core/runtime:3.1
FROM mcr.microsoft.com/dotnet/runtime:7.0
WORKDIR /app
ENTRYPOINT ["dotnet", "Worker.dll"]
COPY --from=builder /out .
COPY --from=build /app .
ENTRYPOINT ["dotnet", "Worker.dll"]

View File

@@ -1,17 +0,0 @@
FROM maven:3.5-jdk-8-alpine AS build
WORKDIR /code
COPY pom.xml /code/pom.xml
RUN ["mvn", "dependency:resolve"]
RUN ["mvn", "verify"]
# Adding source, compile and package into a fat jar
COPY ["src/main", "/code/src/main"]
RUN ["mvn", "package"]
FROM openjdk:8-jre-alpine
COPY --from=build /code/target/worker-jar-with-dependencies.jar /
CMD ["java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-jar", "/worker-jar-with-dependencies.jar"]

View File

@@ -48,8 +48,10 @@ namespace Worker
Console.WriteLine("Reconnecting DB");
pgsql = OpenDbConnection("Server=db;Username=postgres;Password=postgres;");
}
// Normal +1 vote requested
UpdateVote(pgsql, vote.voter_id, vote.vote);
else
{ // Normal +1 vote requested
UpdateVote(pgsql, vote.voter_id, vote.vote);
}
}
else
{
@@ -149,4 +151,4 @@ namespace Worker
}
}
}
}
}

14
worker/Worker.csproj Normal file
View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.2.4" />
<PackageReference Include="Npgsql" Version="4.1.9" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>

View File

@@ -1,16 +0,0 @@
FROM microsoft/dotnet:2.1-sdk-nanoserver-sac2016 as builder
WORKDIR /Worker
COPY Worker/Worker.csproj .
RUN dotnet restore
COPY /Worker .
RUN dotnet publish -c Release -o /out Worker.csproj
# app image
FROM microsoft/dotnet:2.1-runtime-nanoserver-sac2016
WORKDIR /app
ENTRYPOINT ["dotnet", "Worker.dll"]
COPY --from=builder /out .

View File

@@ -1,16 +0,0 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 as builder
WORKDIR /Worker
COPY Worker/Worker.csproj .
RUN dotnet restore
COPY /Worker .
RUN dotnet publish -c Release -o /out Worker.csproj
# app image
FROM mcr.microsoft.com/dotnet/core/runtime:3.1
WORKDIR /app
ENTRYPOINT ["dotnet", "Worker.dll"]
COPY --from=builder /out .

View File

@@ -1,7 +0,0 @@
namespace Worker.Data
{
public interface IVoteData
{
void Set(string voterId, string vote);
}
}

View File

@@ -1,35 +0,0 @@
using Microsoft.Extensions.Logging;
using Worker.Entities;
namespace Worker.Data
{
public class MySqlVoteData : IVoteData
{
private readonly VoteContext _context;
private readonly ILogger _logger;
public MySqlVoteData(VoteContext context, ILogger<MySqlVoteData> logger)
{
_context = context;
_logger = logger;
}
public void Set(string voterId, string vote)
{
var currentVote = _context.Votes.Find(voterId);
if (currentVote == null)
{
_context.Votes.Add(new Vote
{
VoterId = voterId,
VoteOption = vote
});
}
else if (currentVote.VoteOption != vote)
{
currentVote.VoteOption = vote;
}
_context.SaveChanges();
}
}
}

View File

@@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Worker.Entities
{
[Table("votes")]
public class Vote
{
[Column("id")]
[Key]
public string VoterId { get; set; }
[Column("vote")]
public string VoteOption { get; set; }
}
}

View File

@@ -1,19 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace Worker.Entities
{
public class VoteContext : DbContext
{
private static bool _EnsureCreated;
public VoteContext(DbContextOptions options) : base(options)
{
if (!_EnsureCreated)
{
Database.EnsureCreated();
_EnsureCreated = true;
}
}
public DbSet<Vote> Votes { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
using NATS.Client;
using Worker.Messaging.Messages;
namespace Worker.Messaging
{
public interface IMessageQueue
{
IConnection CreateConnection();
void Publish<TMessage>(TMessage message) where TMessage : Message;
}
}

View File

@@ -1,23 +0,0 @@
using Newtonsoft.Json;
using Worker.Messaging.Messages;
using System.Text;
namespace Worker.Messaging
{
public class MessageHelper
{
public static byte[] ToData<TMessage>(TMessage message)
where TMessage : Message
{
var json = JsonConvert.SerializeObject(message);
return Encoding.Unicode.GetBytes(json);
}
public static TMessage FromData<TMessage>(byte[] data)
where TMessage : Message
{
var json = Encoding.Unicode.GetString(data);
return (TMessage)JsonConvert.DeserializeObject<TMessage>(json);
}
}
}

View File

@@ -1,35 +0,0 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NATS.Client;
using Worker.Messaging.Messages;
namespace Worker.Messaging
{
public class MessageQueue : IMessageQueue
{
protected readonly IConfiguration _configuration;
protected readonly ILogger _logger;
public MessageQueue(IConfiguration configuration, ILogger<MessageQueue> logger)
{
_configuration = configuration;
_logger = logger;
}
public void Publish<TMessage>(TMessage message)
where TMessage : Message
{
using (var connection = CreateConnection())
{
var data = MessageHelper.ToData(message);
connection.Publish(message.Subject, data);
}
}
public IConnection CreateConnection()
{
var url = _configuration.GetValue<string>("MessageQueue:Url");
return new ConnectionFactory().CreateConnection(url);
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
namespace Worker.Messaging.Messages
{
public class VoteCastEvent : Message
{
public override string Subject { get { return MessageSubject; } }
public string VoterId {get; set;}
public string Vote {get; set; }
public static string MessageSubject = "events.vote.votecast";
}
}

View File

@@ -1,16 +0,0 @@
using System;
namespace Worker.Messaging.Messages
{
public abstract class Message
{
public string CorrelationId { get; set; }
public abstract string Subject { get; }
public Message()
{
CorrelationId = Guid.NewGuid().ToString();
}
}
}

View File

@@ -1,39 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Worker.Data;
using Worker.Entities;
using Worker.Messaging;
using Worker.Workers;
namespace Worker
{
class Program
{
static void Main(string[] args)
{
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
var loggerFactory = new LoggerFactory()
.AddConsole();
var services = new ServiceCollection()
.AddSingleton(loggerFactory)
.AddLogging()
.AddSingleton<IConfiguration>(config)
.AddTransient<IVoteData, MySqlVoteData>()
.AddTransient<IMessageQueue, MessageQueue>()
.AddSingleton<QueueWorker>()
.AddDbContext<VoteContext>(builder => builder.UseMySQL(config.GetConnectionString("VoteData")));
var provider = services.BuildServiceProvider();
var worker = provider.GetService<QueueWorker>();
worker.Start();
}
}
}

View File

@@ -1,30 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="appsettings.json" />
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
<PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.12" />
<PackageReference Include="NATS.Client" Version="0.8.1" />
</ItemGroup>
</Project>

View File

@@ -1,62 +0,0 @@
using System;
using System.Threading;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NATS.Client;
using Worker.Data;
using Worker.Messaging;
using Worker.Messaging.Messages;
namespace Worker.Workers
{
public class QueueWorker
{
private static ManualResetEvent _ResetEvent = new ManualResetEvent(false);
private const string QUEUE_GROUP = "save-handler";
private readonly IMessageQueue _messageQueue;
private readonly IConfiguration _config;
private readonly IVoteData _data;
protected readonly ILogger _logger;
public QueueWorker(IMessageQueue messageQueue, IVoteData data, IConfiguration config, ILogger<QueueWorker> logger)
{
_messageQueue = messageQueue;
_data = data;
_config = config;
_logger = logger;
}
public void Start()
{
_logger.LogInformation($"Connecting to message queue url: {_config.GetValue<string>("MessageQueue:Url")}");
using (var connection = _messageQueue.CreateConnection())
{
var subscription = connection.SubscribeAsync(VoteCastEvent.MessageSubject, QUEUE_GROUP);
subscription.MessageHandler += SaveVote;
subscription.Start();
_logger.LogInformation($"Listening on subject: {VoteCastEvent.MessageSubject}, queue: {QUEUE_GROUP}");
_ResetEvent.WaitOne();
connection.Close();
}
}
private void SaveVote(object sender, MsgHandlerEventArgs e)
{
_logger.LogDebug($"Received message, subject: {e.Message.Subject}");
var voteMessage = MessageHelper.FromData<VoteCastEvent>(e.Message.Data);
_logger.LogInformation($"Processing vote for '{voteMessage.Vote}' by '{voteMessage.VoterId}'");
try
{
_data.Set(voteMessage.VoterId, voteMessage.Vote);
_logger.LogDebug($"Succesffuly processed vote by '{voteMessage.VoterId}'");
}
catch (Exception ex)
{
_logger.LogError($"Vote processing FAILED for '{voteMessage.VoterId}', exception: {ex}");
}
}
}
}

View File

@@ -1,14 +0,0 @@
{
"MessageQueue": {
"Url": "nats://message-queue:4222"
},
"ConnectionStrings": {
"VoteData": "Server=mysql;Port=4000;Database=votes;User=root;SslMode=None"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,84 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>worker</groupId>
<artifactId>worker</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1200-jdbc41</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<finalName>worker</finalName>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>worker.Worker</mainClass>
<classpathPrefix>dependency-jars/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
<configuration>
<finalName>worker</finalName>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>worker.Worker</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StackExchange.Redis" Version="2.0.601" />
<PackageReference Include="Npgsql" Version="4.0.9" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
</Project>

View File

@@ -1,102 +0,0 @@
package worker;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisConnectionException;
import java.sql.*;
import org.json.JSONObject;
class Worker {
public static void main(String[] args) {
try {
Jedis redis = connectToRedis("redis");
Connection dbConn = connectToDB("db");
System.err.println("Watching vote queue");
while (true) {
String voteJSON = redis.blpop(0, "votes").get(1);
JSONObject voteData = new JSONObject(voteJSON);
String voterID = voteData.getString("voter_id");
String vote = voteData.getString("vote");
System.err.printf("Processing vote for '%s' by '%s'\n", vote, voterID);
updateVote(dbConn, voterID, vote);
}
} catch (SQLException e) {
e.printStackTrace();
System.exit(1);
}
}
static void updateVote(Connection dbConn, String voterID, String vote) throws SQLException {
PreparedStatement insert = dbConn.prepareStatement(
"INSERT INTO votes (id, vote) VALUES (?, ?)");
insert.setString(1, voterID);
insert.setString(2, vote);
try {
insert.executeUpdate();
} catch (SQLException e) {
PreparedStatement update = dbConn.prepareStatement(
"UPDATE votes SET vote = ? WHERE id = ?");
update.setString(1, vote);
update.setString(2, voterID);
update.executeUpdate();
}
}
static Jedis connectToRedis(String host) {
Jedis conn = new Jedis(host);
while (true) {
try {
conn.keys("*");
break;
} catch (JedisConnectionException e) {
System.err.println("Waiting for redis");
sleep(1000);
}
}
System.err.println("Connected to redis");
return conn;
}
static Connection connectToDB(String host) throws SQLException {
Connection conn = null;
try {
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://" + host + "/postgres";
while (conn == null) {
try {
conn = DriverManager.getConnection(url, "postgres", "postgres");
} catch (SQLException e) {
System.err.println("Waiting for db");
sleep(1000);
}
}
PreparedStatement st = conn.prepareStatement(
"CREATE TABLE IF NOT EXISTS votes (id VARCHAR(255) NOT NULL UNIQUE, vote VARCHAR(255) NOT NULL)");
st.executeUpdate();
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.exit(1);
}
System.err.println("Connected to db");
return conn;
}
static void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
System.exit(1);
}
}
}