Calculator service done in .net 7 as a GRPC service with json transcoding. Directions didn't specify, but I assumed the calculator should honor order of operations. See calculator.proto for the service contract protobuf.
There is float/double support in proto, but no native Decimal support, so a class with a utility implicit conversion operator has been built in. I may rework this to double just to make request/response model values and translations easier.
Operations are passed to the calculator service as a collection of objects that have an operand and an operation_type. Each operand is supplied with a units and nanos value to attain decimal precision. units represents the whole units part of the amount, and nanos represents the decimal precision using a range of possible values from 0.999_999_999 to -0.999_999_999. To represent 1.37, units would be 1 and nanos would be 37000000. To represent 2, units would be 2 and nanos would simply be 0. See Microsoft documentation for more details.
operation_type must be a value belonging to the CalculatorOperationType enum defined in calculator.proto. I haven't found how the service will take a string value for the enum, so in my testing I've been supplying the enum int value.
An example of an equation represented by operand + operation type pair values can be seen in the example json POST request body below: 1.37 + 2 * 3 - 4 / 2 = x
[1.37,+] [2,*] [3,-] [4,/] [2,=] x
{
"calculator_operation": [
{
"operand": {
"units": 1,
"nanos": 37000000
},
"operation_type": 0
},
{
"operand": {
"units": 2,
"nanos": 0
},
"operation_type": 2
},
{
"operand": {
"units": 3,
"nanos": 0
},
"operation_type": 1
},
{
"operand": {
"units": 4,
"nanos": 0
},
"operation_type": 3
},
{
"operand": {
"units": 2,
"nanos": 0
},
"operation_type": 4
}
]
}
Maintaining order of operations, the steps to evaluate this equation would be:
- 2 * 3 = 6
- 4 / 2 = 2
- 1.37 + 6 = 7.37
- 7.37 - 2 = 5.37
The service will return a HTTP 200 response with a success field of false and an appropriate message if the application experiences an error or if the request supplied is considered invalid for the following reasons:
- if zero or only one operation is supplied
- more than 5 operations are supplied
- if the collection of operations has multiple instances of the operation type
CalculatorOperationType.Evaluatesupplied (essentially more than one = sign) - if the collection of operations has an instance of the operation type
CalculatorOperationType.Evaluatesomewhere other than the final operation supplied - if the collection of operations does not have an instance of the operation type
CalculatorOperationType.Evaluatesupplied
If the request fields are supplied in incorrect format (ex: units is supplied as a string), the generated proto code itself currently throws a 400 Bad Request with some detail about what went wrong.
If this application is run locally, it will accept https requests on port 7232. The routing path is defined in calculator.proto - full example URL running locally would be https://localhost:7232/v1/calculator. The request is currently an http POST request with a json request body.
One side project goal I'm working towards in these repositories is replacing a physical binder that contains many recipes for beers I have brewed at home.
This directory contains a Spring Boot Java application with some JPA wrangling for beer recipe models that are persisted in a local MySQL instance.
Going back to 2019, I think there's some limited REST functionality here and a bit of yuckyness (ex. the database username/pw are stored in plain text config).
This directory contains a .net solution with some projects - mostly .net 7-targeted gRPC services. In a microservice application world, each project would likely live as its own separate application. The protobuf / contract projects would probably be deployed as nuget packages and the gRPC services themselves deployed to some sort of CI/CD cloud presence, but I don't intend to pay $ for these apps and will rather run them locally.
This solution has a DbUp project with all of the MySQL scripts stored in version control so all that should be needed to run this application locally is
- supply a MySQL connection string in config (I'm using a secrets.json override, you could add it to launchSettings config) with the name "ConnectionStrings:MySql.Beer_Net" that has a value of a MySQL connection string pointing at an available db instance pointing at a Database named beer_net
- run the DbUp project manually and any previously-not-run .sql scripts should execute (this is controlled by a .schemaversions table that gets created by DbUp on your db)
Example of my secrets.json:
{
// these normally would be stored in something like Azure app config + Azure key vault but that costs me $$
"ConnectionStrings": {
"MySql.Beer_Net": "Server=localhost;Database=beer_net;Uid=some_user_id;Pwd=some_password;"
}
}
I am using Tye (https://github.com/dotnet/tye) to run the microservices on Windows - once installed, in the root directory with the tye.yaml config, you can run tye run and Tye will spin up the applications and assign them ports dynamically.
A tye GetServiceUri extension method is used in Startup.cs for pointing clients to their respective RPC services (Tye essentially does this by smushing together three environment variables - protocol, hostname, port - that get dynamically generated).
The top-level BeerStuff.Api project represents the external-facing application that web clients would interact with. I am playing around with .net 7's gRPC JSON transcoding here so there's no standard REST MVC "Controller" class - code is generated by means of the RPC services and routing defined in the .proto files.
Much of this is overblown for the simple CRUD operations happening, and I may explore less redundant ways of implementing moving forward.
Next steps on the list:
unit testsfix MySQL timestamp -> c# DateTime weirdness (currently the values are stored as UTC on a POST but a further conversion adds another +x offset hours onto the same value on a subsequent GET)- add nameContains url param searching & limit response record limiting to GET BeerGrain
- flesh out remaining CRUD functionality for BeerGrain & expand to the other entities / tables
This directory contains a Spring Boot project that has a REST API with a singular endpoint to add new golf score results hole-by-hole. The project also contains a Kafka producer exposed via the POST endpoint, and a Kafka consumer that responds to an event of a hole score by persisting it to a MySQL database.
This isn't necessarily the best usage of Kafka, but I wanted a simple project to play around with Kafka a bit. I also discovered that the app I use for GPS while playing golf (that also tracks scores) offers a CSV data dump for free, and wanted to slice and dice my score data a bit. While maybe not a traditional/primary use of Kafka, I can see exposing APIs for event processing as potentially useful in working with legacy/existing application landscapes that rely on traditional RDBMS persistence.
I'm also using this as an opportunity to get more experience using Cursor/AI in greenfield development. I've been using Cursor in my day-to-day work for bug fixes and porting legacy code in modernization efforts, but have not had much opportunity yet to explore standing up new applications from scratch using Cursor's assistance.
This application assumes a Kafka broker is running on localhost:9092 and a MySQL instance running on localhost:3306 with a db named golf-stats . The scripts to create the tables in the database and some scripts to hydrate those tables from a CSV dump are in the /scripts directory (the CSV here has a few sample rounds from my larger data dump). SQL auth would need to be defined in application.properties.
Next steps:
- add a front-end with some score summary information and the ability to upload new scores via updated CSV dumps from the golf scoring app, potentially some interesting stat/score panels, etc
- add test coverage