mirror of
https://github.com/dockersamples/example-voting-app.git
synced 2026-07-11 01:19:17 +00:00
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:
24
README.md
24
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,2 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.apt.aptEnabled=false
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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
14
worker/Worker.csproj
Normal 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>
|
||||
@@ -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 .
|
||||
@@ -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 .
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Worker.Data
|
||||
{
|
||||
public interface IVoteData
|
||||
{
|
||||
void Set(string voterId, string vote);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "*"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user