This is a coding challenge I did in the past to learn Go, and thus it is the first app I have written in Go.
The application loads the requests from the input.txt
file and outputs the resolved requests to output-test.txt
. If you run go test
, it also runs generates the file main_test.go tests that the final output-test.txt
and output.txt
files match.
I started off by planning how I would approach the problem. Initially, I was going to make a more complex version of this app that could have a input/state/output functions that would each accept an interface/interfaces. The idea was that you could, via dependency injection, provide the desired implementation of each part (e.g. in-memory database for state, vs. an actual persistent database vs. a filesystem based solution). However, I quickly realized how large a task this would be, especially in a new language, and instead opted to focus on the core design with good testing. Of input, state, and output, I only used interfaces on the state layer, with two interfaces: ResponseLimits
and VelocityLimits
. If you wanted to change the implementation of my ad-hoc in-memory database and table (FundSuccessDB
and ResponsesTable
) to real databases, you could create new types that implement ResponseLimits
and VelocityLimits
, that manage and fetch data from real databases. After creating the new interface implementations, you wouldn't need to modify anything else in the application except for the fields you pass to the checkConditions function for the interfaces ResponseLimits
and VelocityLimits
.
This app loads the data into a custom array of FundRequest
structs using JSON de-serialization of the individual request strings. Those form the input model, or request fields, which then get resolved into FundResponse
structs with true/false Accepted
field state, depending upon whether the request passes the velocity limit conditions and other conditions. I set it up so that it would be relatively easy to change the input/output layers to an HTTP server that receives and acts on individual requests, so long as you use the same model objects. Although on an HTTP server you would make use of the handleRequest
receiver method of FundRequest
, rather than handleBatchRequestsWriteToFile
which was written specifically to operate on batches of requests (in ascending time order).
Assuming you have Go already installed, to run the application, call go run .
. You can see my Go version in go.mod
, if you need to check for compatibility. It should generate an output-test.txt
file, that is identical to output.txt
.
To Test the application, call go test
, which will run tests in main_test.go
, state_memory_db_successes.go
, and state_memory_table_responses.go
. main_test.go
should also generate a new output-test.txt
file, that is identical to output.txt
(the test will fail if it isn't).
line 687 of the output-test.txt has an extra line with the value: {"id":"6928","customer_id":"562","accepted":true} It is the only discrepancy between output.txt and output-test.txt. There appears to be a duplicate id on the same customer, so see why it wasn't filtered out. FIXED: I needed another table, the Responses table, since non-approved responses aren't logged in the Success table (which is used for checking velocity limits on amounts/transactions).
I finished adding tests for the ResponsesTable and FundSuccessTable. I also fixed the length of the history for weekly limits (it should only go back to Monday of the current week at 00:00:00).
Create only one version given the time constraints, but design it using an interface that can be used by other versions for the persistence layer (I.e. if the persistence layer was a DB, the Go wrapper for it could implement the Limits interface). That will accomplish the extensible design I'm going for, while keeping complexity low, as this project is already taking quite a bit of my afterwork time. I'd rather polish the core design and testing of this app than try to cram more features into it.
Completed most of Stephen Girder's "Go: The Complete Developer's Guide (Golang)" on Udemy over the weekend. Start the implementation now by starting with the simplest version of the application.
Make it runnable with different command line args (simplest version first - easiest to implement):
EDIT: following the schema below, I actually implemented "local_file_memory_file" (not listed), since my completed solution runs locally (not deployed anywhere), loads data from a local file (input.txt), persists state in memory, and outputs to a local file (output-test.txt).
x. "{where it runs} _ {data input} _ {state persistence} _ {data output}"
- "local_file_memory_std" Runs locally, loading data from local file, persisting state in memory, outputting in std io.
- "local_file_file_file" Runs locally, loading data from local file, persisting state in local files, outputting in file.
- "local_http_file_http" Runs locally, receiving data in HTTP request, persisting state in local files, outputting in HTTP response.
These can be made more customizable with a config file. The config file can be written in Go, to allow for easy dependency injection, which would also make it easily configurable, particularly if I use seemingly common built-in Go interfaces like Reader and Writer. I will put the business logic in a separate package/module.
Read: https://medium.com/rungo/anatomy-of-modules-in-go-c8274d215c16
These would rightfully be interpreted as scope creep, but otherwise good ideas:
4. "gae_http_cloudsql_http" Runs on Google App Engine, receiving data in HTTP request, persisting state to Cloud SQL DB, outputting by HTTP response.
- Out-of-scope, because while I might be able to deploy a barebones GAE Go app in time, I wouldn't be able to spend the time necessary to secure it; and the assignment asked for no public hosting of the solution. I will assume that extends to the deployed solution, not just source code.
- Have a config to run the application as a web app, and create another web app to send the original web app the input (a simple version of this could make a good test case for the local_http_file_http scenario though).
- Purge db data older than a month (possibly with Cron job).
- If I am getting into lifecycle management of data, I should be asking questions like "how long should my data persist for business, or regulatory reasons? Should there be backups? Etc."; but that level of detail doesn't exist for this problem. However, this would be a question to ask in a follow-up meeting with the client, product owner, lawyer, or stakeholder.
In finance, it's common for accounts to have so-called "velocity limits". In this task, you'll write a program that accepts or declines attempts to load funds into customers' accounts in real-time.
Each attempt to load funds will come as a single-line JSON payload, structured as follows:
{
"id": "1234",
"customer_id": "1234",
"load_amount": "$123.45",
"time": "2018-01-01T00:00:00Z"
}
Each customer is subject to three limits:
- A maximum of $5,000 can be loaded per day
- A maximum of $20,000 can be loaded per week
- A maximum of 3 loads can be performed per day, regardless of amount
As such, a user attempting to load $3,000 twice in one day would be declined on the second attempt, as would a user attempting to load $400 four times in a day.
For each load attempt, you should return a JSON response indicating whether the fund load was accepted based on the user's activity, with the structure:
{ "id": "1234", "customer_id": "1234", "accepted": true }
You can assume that the input arrives in ascending chronological order and that if a load ID is observed more than once for a particular user, all but the first instance can be ignored. Each day is considered to end at midnight UTC, and weeks start on Monday (i.e. one second after 23:59:59 on Sunday).
Your program should process lines from input.txt
and return output in the format specified above, either to standard output or a file. Expected output given our input data can be found in output.txt
.