Обязанности

  • Разработка серверной части web-приложений (.NET Core, PostgreSQL, gRPC)

Требования

  • Базовые знания C# и .NET Core.
  • Теоретические знания хотя бы одного из фреймворков для разработки веб-приложений (можно не на .NET Core).
  • Готовность к изучению множества технологий разработки одновременно в свое личное время.
  • Приветствуется владение чем-нибудь из перечисленного: SQL, HTML, CSS (также lesscss, sass), Linux CLI, Docker, git, VS Code (или другие IDE/редакторы).
  • Приветствуется базовая грамотность в Computer Science, включая базы данных, сетевые технологии, технологические стеки построения веб-приложений, устройства операционных систем (в первую очередь семейства Linux). Ориентиры: https://yollection.ru/road/backend, https://yollection.ru/road/frontend.

Задание 1. Разработать функцию определения счета в игре

Задача

В примере кода ниже генерируется список фиксаций состояния счета игры в течение матча.
Разработайте функцию Game.getScore(offset), которая вернет счет на момент offset.
Нужно суметь понять суть написанного кода, заметить нюансы, разработать функцию вписывающуюся стилем в существующий код, желательно адекватной алгоритмической сложности.

namespace Task1
{
class App
{
static void Main(string[] args)
{
Game.task1();
}
}
public struct Score
{
public int home;
public int away;

public Score(int home, int away)
{
this.home = home;
this.away = away;
}
}

public struct GameStamp
{
public int offset;
public Score score;
public GameStamp(int offset, int home, int away)
{
this.offset = offset;
this.score = new Score(home, away);
}
}

public class Game
{
const int TIMESTAMPS_COUNT = 50000;

const double PROBABILITY_SCORE_CHANGED = 0.0001;

const double PROBABILITY_HOME_SCORE = 0.45;

const int OFFSET_MAX_STEP = 3;

GameStamp[] gameStamps;

public Game()
{
this.gameStamps = new GameStamp[] { };
}

public Game(GameStamp[] gameStamps)
{
this.gameStamps = gameStamps;
}

GameStamp generateGameStamp(GameStamp previousValue)
{
Random rand = new Random();

bool scoreChanged = rand.NextDouble() > 1 - PROBABILITY_SCORE_CHANGED;
int homeScoreChange = scoreChanged && rand.NextDouble() > 1 - PROBABILITY_HOME_SCORE ? 1 : 0;
int awayScoreChange = scoreChanged && homeScoreChange == 0 ? 1 : 0;
int offsetChange = (int)(Math.Floor(rand.NextDouble() * OFFSET_MAX_STEP)) + 1;

return new GameStamp(
previousValue.offset + offsetChange,
previousValue.score.home + homeScoreChange,
previousValue.score.away + awayScoreChange
);
}

static Game generateGame()
{
Game game = new Game();
game.gameStamps = new GameStamp[TIMESTAMPS_COUNT];

GameStamp currentStamp = new GameStamp(0, 0, 0);
for (int i = 0; i < TIMESTAMPS_COUNT; i++)
{
game.gameStamps[i] = currentStamp;
currentStamp = game.generateGameStamp(currentStamp);
}

return game;
}

public static void task1()
{
Game game = generateGame();
game.printGameStamps();
}

void printGameStamps()
{
foreach (GameStamp stamp in this.gameStamps)
{
Console.WriteLine($"{stamp.offset}: {stamp.score.home}-{stamp.score.away}");
}
}

public Score getScore(int offset)
{
// continue the function's implementation
}
}
}

Результат

  1. Ссылка на gist с исходным кодом функции.

Задание 2. Разработать тесты для функции определения счета в игре

Задача

Для разработанной в предыдущем задании функции Game.getScore(offset) разработайте unit-тесты на базе библиотеки XUnit.
Тесты должны учитывать все возможные случаи использования функции, концентрироваться на проверке одного случая, не повторяться, название тестов должно отражать суть выполняемой проверки.

Результат

  1. Ссылка на gist с исходным кодом тестов.

Задание 3. Разработать gRPC сервис биллинга

Задача

Разработайте сервис биллинга, с возможностью эмиссии внутренней валюты (Coins, или Монеты) приложения, передачи валюты от пользователя к пользователю, находить монету с самой длинной историей перемещения.

Прото-файл сервиса

# billing.proto
syntax = "proto3";

package billing;

option csharp_namespace = "Billing";

service Billing {
rpc ListUsers (None) returns (stream UserProfile);
rpc CoinsEmission (EmissionAmount) returns (Response);
rpc MoveCoins (MoveCoinsTransaction) returns (Response);
rpc LongestHistoryCoin (None) returns (Coin);
}

message None {
}

message UserProfile {
string name = 1;
optional int64 amount = 2;
}

message MoveCoinsTransaction {
string src_user = 1;
string dst_user = 2;
int64 amount = 3;
}

message Response {
enum Status {
STATUS_UNSPECIFIED = 0;
STATUS_OK = 1;
STATUS_FAILED = 2;
}

Status status = 1;
string comment = 2;
}

message EmissionAmount {
int64 amount = 1;
}

message Coin {
int64 id = 1;
string history = 2;
}
  • Billing.ListUsers() – перечисляет пользователей в сервисе. При инициализации создайте в сервисе следующих пользователей, поле rating потребуется при эмиссии:
[
{
"name": "boris",
"rating": 5000
},
{
"name": "maria",
"rating": 1000
},
{
"name": "oleg",
"rating": 800
}
]
  • Billing.CoinsEmission() – распределяет по пользователям amount монет, учитывая рейтинг. Пользователи получают количество монет пропорциональное рейтингу, при этом каждый пользователь должен получить не менее 1-й монеты. Каждая монета имеет свой id и историю перемещения между пользователями, начиная с эмиссии.
  • Billing.MoveCoins() – перемещает монеты от пользователя к пользователю если у пользователя-источника достаточно монет на балансе, в противном случае возвращает ошибку.
  • Billing.LongestHistoryCoin() – возвращает монету с самой длинной историей перемещения между пользователями.

Замечания к реализации

Для разработки серверной части следует использовать .NET Core 6.
Интегрировать базу данных в этом задании не стоит, достаточно хранить информацию в памяти сервиса, инициализируя его данными пользователей при каждом запуске.

Результат

  1. Ссылка на gist с результатом работы следующего скрипта:
#!/usr/bin/env bash

GRPCSERVER=localhost:5238
PROTODIR=../Protos

echo "List users. Empty balances."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers

echo "Emission 10 coins."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{"amount": "10"}' $GRPCSERVER billing.Billing/CoinsEmission

echo "List users. After the emission."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers

echo "Move 5 coins."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{"amount":"5","dst_user":"maria","src_user":"boris"}' $GRPCSERVER billing.Billing/MoveCoins

echo "List users. After the coins movement."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/ListUsers

echo "A coin with the longest history."
grpcurl -import-path $PROTODIR -proto billing.proto -plaintext -d \
'{}' $GRPCSERVER billing.Billing/LongestHistoryCoin
  1. Ссылки на git-репозитории с исходными кодами решения.