By Marc Nguyen and Jean-Baptiste Rubio.
Specifications are given here: Protos and docs
Fetch data from the api and display in a list and a screen with the details
Possibility to bookmark certain items per user
OAuth Authentication
Mockup:
Implementation of a search/filter system on the displayed list
Setting up a local database to display the item list in offline mode
Usage of StateFlow
Use docker/kubernetes/openshift to deploy the container.
docker pull ghcr.io/darkness4/train-station-api:latest
Available arch are: arm64
and amd64
.
An example of docker-compose.yml:
version: '3.9'
services:
train-station-api:
build: ghcr.io/darkness4/train-station-api:amd64
ports:
- 3000:3000
volumes:
- ./db:/db
environment:
JWT_SECRET: <base64 secret>
LISTEN_ADDRESS: 0.0.0.0:3000
DB_PATH: /db/db.sqlite3
DEBUG: true
TLS_ENABLE: false
Install golang et install the dependencies
go mod download
# Inside: ./train-station-api
make
./bin/train-station-api
make unit # Run unit tests
flowchart TD
JWT
*sql.SQL
DB
subgraph server[gRPC server]
healthAPI
stationAPI
authAPI
end
*sql.SQL --> DB
DB --> stationAPI
JWT --> authAPI
JWT --> stationAPI
If you have seen the old versions before version 2, we were using the SOLID architecture in Go. After years of experience, we realized that the SOLID architecture tells us how to organize our code and how to inject dependencies.
However, the explicit layering adds standard code and incomprehensible "data mappings", which hinders maintainability and understanding of the project. While the SOLID architecture seems ideal for object-oriented languages such as Kotlin, for Go it adds too much boilerplate code with no benefit other than having to "pseudo-satisfy" the SOLID principles.
In reality, the SOLID priciples goes against the Effective Go recommendations which is way more important since it is the base for every Go developers, while SOLID are principles for object-oriented programming.
Since SOLID offers no real benefits outside of pain, we decided to remove the explicit layering while still sticking to domain-oriented development.
The contract is as follows:
This translates into :
main.go
as the main
function indicates the start. The data is stored in a database or cache.favoriteSetter
interface and implement it. And the database can implement the interface perfectly.erDiagram
Station }|..|{ User : favorite
Install pnpm and install the dependencies:
pnpm install --frozen-lockfile
Create a Github OAuth App, generate a secret with openssl rand -base64 32
, and fill a .env
file with the following content:
GITHUB_ID=<Github OAUTH App ID>
GITHUB_SECRET=<Github OAUTH App Secret>
AUTH_SECRET=<Random Secret>
Serve in development mode:
pnpm run dev
Or deploy in production:
pnpm run build
# pnpm run preview # for demonstration
flowchart TD
oauth[OAuth provider]
subgraph data[Data Layer]
subgraph cache[Cache]
oauthDataStore
jwtDataStore
Room
end
oauth
StationRepositoryImpl
authAPI
stationAPI
end
subgraph domain[Domain Layer]
StationRepository
end
subgraph presentation[Presentation Layer]
LoginViewModel
DetailViewModel
StationListViewModel
MainActivity
end
Room-->StationRepositoryImpl
jwtDataStore-->StationRepositoryImpl
StationRepositoryImpl-->|implements|StationRepository
stationAPI-->StationRepositoryImpl
jwtDataStore-->LoginViewModel
authAPI-->LoginViewModel
oauthDataStore-->LoginViewModel
StationRepository-->DetailViewModel
StationRepository-->StationListViewModel
oauth-->MainActivity
oauthDataStore-->MainActivity
The Data layer:
Stations
PagingSource
. The PagingSource
is able to load pages of data stored in a PagingData
.Stations
. It needs a JWT token to fetch datas.StationRepositoryImpl
implements StationRepository
and executes CRUD methods.Station
of the response is cached and returned.watch
/watchOne
), we observe the cache and may fetch the initial values from a data source.Pager
to retrieve the PagingData
from the cache. The pager uses the StationRemoteMediator
which is responsible to fetch and cache pages of Station
from a data source.In the Domain layer:
stationRepository
satisfies most use cases (displaying a list of Stations
, displaying details of a Station
, updating a Station
...).In the Presentation layer :
ViewModels
. The ViewModels
act as the middle man between the presentation layer and domain layer. This is to follow the Modern Android App Architecture.MainActivity
renders a Scaffold
with its TopAppBar
. Inside that scaffold is a NavigationHost
composable.NavigationHost
renders a page based on a route:/login
, and shows a login button. The button triggers a redirection to the OAuth provider, which then send the resulting OAuth Access Token to the MainActivity
and triggers the authAPI
to fetch a JWT. Upon receiving a JWT, the user is authenticated and is redirected to the /stations
route./stations
route shows a LazyColumn
which listen to a Flow<PagingData<Station>>
. This allows lazy loading of the data, and therefore, the lazy loading of "station cards". The page also shows a "About" page. When the user push on a "station card", the user is redirected to the /details
route./details
route shows the position of the train station on Google Maps and details about that station on a Bottom Sheet.MIT License
Copyright (c) 2021 Marc NGUYEN, Jean-Baptiste RUBIO
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.