I was given task to build a client server application, using any technology I want. Here are the requirements
- To simplify the process the Server would have an in memory stock list and there would be a random data generator to update the stock data
- Each client will have his own stock list
- Each client can add stocks to its list
- To simplify the process each client will use polling to get the stock prices, but the server needs to return only the changed stocks and no the whole list.
- There will be an option to add new stocks to the repository.
I am focusing more on the server side here and for now the client is a console app. each client generates a token (guid) and sends it to the server as his ID. I used Nancy http server as the back end.
I would like you to please comment about the correctness of my implementation as if it was a code review for your team. OOP design, efficient and safe server implementation
I would appreciate any comments or questions.
1.Server Project
ServerModule.cs
namespace StocksApp
{
public class ServerModule : Nancy.NancyModule
{
public ServerModule()
{
Post["/User/{id}"] = parameters => CreateUser(parameters);
Get["/UserShares/{id}"] = parameters => GetUserShares(parameters);
Post["/AddShareToUser/{id}/{share}"] = parameters => RegisterUserToShare(parameters);
Post["/AddShareToRepository/{id}"] = parameters => AddShareToRepository(parameters);
}
private dynamic AddShareToRepository(dynamic parameters)
{
string id = parameters["id"].ToString();
if (!string.IsNullOrEmpty(id))
{
if (!RepositoriesFactory.StocksRepository.AddStock(id))
{
return JsonConvert.SerializeObject(new SimpleResponse() { IsSuccess = false });
}
return JsonConvert.SerializeObject(new SimpleResponse() { IsSuccess = true });
}
return JsonConvert.SerializeObject(new SimpleResponse
{
IsSuccess = false,
Message = "Id null"
});
}
private static dynamic RegisterUserToShare(dynamic parameters)
{
try
{
string id = parameters["id"].ToString();
if (!string.IsNullOrEmpty(id))
{
User user = RepositoriesFactory.UsersRepository.GetUser(id);
if (user.RegisterToStockUpdated(parameters["share"]))
{
return JsonConvert.SerializeObject(new SimpleResponse() { IsSuccess = true });
}
return JsonConvert.SerializeObject(new SimpleResponse() { IsSuccess = false, Message = "Could not register user" });
}
return JsonConvert.SerializeObject(new SimpleResponse
{
IsSuccess = false,
Message = "Id null"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new SimpleResponse
{
IsSuccess = false,
Message = e.ToString()
});
}
}
private static dynamic GetUserShares(dynamic parameters)
{
try
{
string id = parameters["id"].ToString();
if (!string.IsNullOrEmpty(id))
{
User user = RepositoriesFactory.UsersRepository.GetUser(id);
string jsonData =
JsonConvert.SerializeObject(new StockListResponse
{
Stocks = user.GetUpdatedStocks(),
IsSuccess = true
});
return jsonData;
}
return JsonConvert.SerializeObject(new StockListResponse
{
IsSuccess = false,
Message = "Id null"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new StockListResponse
{
IsSuccess = false,
Message = e.ToString()
});
}
}
private static dynamic CreateUser(dynamic parameters)
{
try
{
string id = parameters["id"].ToString();
if (!string.IsNullOrEmpty(id))
{
RepositoriesFactory.UsersRepository.AddUser(id);
return JsonConvert.SerializeObject(new SimpleResponse { IsSuccess = true });
}
return JsonConvert.SerializeObject(new SimpleResponse
{
IsSuccess = false,
Message = "Id null"
});
}
catch (Exception e)
{
return JsonConvert.SerializeObject(new SimpleResponse
{
IsSuccess = false,
Message = e.ToString()
});
}
}
}
}
RepositoriesFactory.cs
namespace StocksApp
{
public static class RepositoriesFactory
{
public static IUsersRepository UsersRepository { get;}
public static IStocksRepository StocksRepository{ get; }
static RepositoriesFactory()
{
StocksRepository = new StocksRepository();
UsersRepository = new UsersRepository(StocksRepository);
}
}
}
IStocksRepository
namespace StocksApp.Repositories
{
public interface IStocksRepository
{
void StartUpdats();
IEnumerable<string> StockList { get; }
Stock GetStock(string stockID);
bool AddStock(string id);
}
}
StocksRepository.cs
namespace StocksApp.Repositories
{
public class StocksRepository : IStocksRepository
{
public ConcurrentDictionary<string, Stock> Stocks { get; set; }
public Stock GetStock(string stockID)
{
if (Stocks.ContainsKey(stockID))
{
return Stocks[stockID];
}
return null;
}
public IEnumerable<string> StockList => Stocks.Keys;
public StocksRepository()
{
Stocks = new ConcurrentDictionary<string, Stock>(
new Dictionary<string, Stock> {
{"INTC", new Stock("INTC") },
{"GOOG", new Stock("GOOG") },
{"MSC", new Stock("MSC") },
{"AMD", new Stock("AMD") },
{"AAPL", new Stock("AAPL") }}
);
}
public bool AddStock(string id)
{
return Stocks.TryAdd(id, new Stock(id));
}
public void StartUpdats()
{
Random r = new Random();
Task.Run(() =>
{
while (true)
{
int index = r.Next(0, Stocks.Keys.Count);
double value = Math.Round(r.NextDouble() * 50, 2);
string stockName = StockList.ElementAt(index);
Console.WriteLine($"{stockName} was updated to {value}");
Stocks[stockName].UpdateValue(value);
Thread.Sleep(500);
}
});
}
}
}
IUsersRepository.cs
namespace StocksApp.Repositories
{
public interface IUsersRepository
{
void AddUser(string id);
User GetUser(string id);
void RegisterForUpdated(string userID, string stockID);
}
}
UsersRepository.cs
namespace StocksApp.Repositories
{
public class UsersRepository : IUsersRepository
{
private readonly IStocksRepository _stockRepository;
private Dictionary<string, User> _users = new Dictionary<string, User>();
public UsersRepository(IStocksRepository stockRepository)
{
_stockRepository = stockRepository;
}
public void AddUser(string id)
{
_users[id] = new User("id",_stockRepository);
}
public User GetUser(string id)
{
if (_users.ContainsKey(id))
{
return _users[id];
}
return null;
}
public void RegisterForUpdated(string userID, string stockID)
{
if (_users.ContainsKey(userID))
{
_users[userID].RegisterToStockUpdated(stockID);
}
}
}
}
User.cs
namespace StocksApp.Repositories
{
public class User
{
private readonly IStocksRepository _stockRepository;
private readonly ConcurrentDictionary<string,Stock> _registeredStocks = new ConcurrentDictionary<string,Stock>();
private readonly IDictionary<string, double> _changedStocks = new Dictionary<string, double>();
private readonly object _locker = new object();
public string ID { get; }
public User(string id, IStocksRepository stockRepository)
{
_stockRepository = stockRepository;
ID = id;
}
public bool RegisterToStockUpdated(string stockID)
{
//so we dont register twice.
if (_registeredStocks.ContainsKey(stockID))
{
return false;
}
Stock stock = _stockRepository.GetStock(stockID);
if (stock != null)
{
stock.Updated += Stock_Updated;
_registeredStocks.TryAdd(stockID,stock);
return true;
}
return false;
}
private void Stock_Updated(Stock stock)
{
lock (_locker)
{
_changedStocks[stock.Name] = stock.Value;
}
}
public IDictionary<string, double> GetUpdatedStocks()
{
IDictionary<string, double> returnValue;
lock (_locker)
{
returnValue = new Dictionary<string, double>(_changedStocks);
_changedStocks.Clear();
}
return returnValue;
}
}
}
there is also the bootstrapper for the console server application
Program.cs
namespace StocksApp
{
class Program
{
private readonly IStocksRepository _stocksRepository;
private readonly IUsersRepository _usersRepository;
private string _url = "http://localhost";
private int _port = 8080;
private NancyHost _nancy;
public Program(IStocksRepository stocksRepository, IUsersRepository usersRepository)
{
_stocksRepository = stocksRepository;
_usersRepository = usersRepository;
var uri = new Uri($"{_url}:{_port}/");
var configuration = new HostConfiguration()
{
UrlReservations = new UrlReservations() { CreateAutomatically = true }
};
_nancy = new NancyHost(configuration,uri);
}
private void Start()
{
_nancy.Start();
_stocksRepository.StartUpdats();
Console.WriteLine($"Started listennig port {_port}");
Console.ReadKey();
_nancy.Stop();
}
static void Main(string[] args)
{
var p = new Program(RepositoriesFactory.StocksRepository, RepositoriesFactory.UsersRepository);
p.Start();
}
}
}
2. SharedProject
Stock.cs
namespace StoksApp.Shared.Entities
{
public class Stock
{
public string Name { get; set; }
public double Value { get; set; }
public event Action<Stock> Updated = delegate { };
public Stock(string name)
{
Name = name;
}
public void UpdateValue(double value)
{
Value = value;
Updated(this);
}
}
}
SimpleResponse.cs
namespace StoksApp.Shared.Entities
{
public class SimpleResponse
{
public bool IsSuccess { get; set; }
public string Message { get; set; }
}
}
StockListResponse.cs
namespace StoksApp.Shared.Entities
{
public class StockListResponse : SimpleResponse
{
public IDictionary<string, double> Stocks { get; set; }
}
}