We have a pretty limited set of abstractions that are used throughout. We mostly serve web requests, talk to a PostgreSQL database, communicate with 3rd-party systems with HTTP, and we're starting to use Temporal.io for queued-job type stuff over a homegrown queueing system that we used in the past.
One of the things you'll often hear as a critique levelled against Haskell developers is that we tend to overcomplicate things, but as an organization we skew very heavily towards favoring simple Haskell, at least at the interface level that other developers need to use to interact with a system.
So yeah, basically: Web Request -> Handler -> Do some DB queries -> Fire off some async work.
We also have risk analysis, cron jobs, batch processing systems that use the same DB and so forth.
We're starting to feel a little more pain around maybe not having enough abstraction though. Right now pretty much any developer can write SQL queries against any tables in the system, so it makes it harder for other teams to evolve the schema sometimes.
For SQL, we use a library called esqueleto, which lets us write SQL in a typesafe way, and we can export fragments of SQL for other developers to join across tables in a way that's reusable:
select $
from $ \(p1 `InnerJoin` f `InnerJoin` p2) -> do
on (p2 ^. PersonId ==. f ^. FollowFollowed)
on (p1 ^. PersonId ==. f ^. FollowFollower)
return (p1, f, p2)
which generates this SQL:
SELECT P1., Follow., P2.*
FROM Person AS P1
INNER JOIN Follow ON P1.id = Follow.follower
INNER JOIN Person AS P2 ON P2.id = Follow.followed
^ It's totally possible to make subqueries, join predicates, etc. reusable with esqueleto so that other teams get at data in a blessed way, but the struggle is mostly just that the other developers don't always know where to look for the utility so they end up reinventing it.
In the end, I guess I'd assert that discoverability is the trickier component for developers currently.
One of the things you'll often hear as a critique levelled against Haskell developers is that we tend to overcomplicate things, but as an organization we skew very heavily towards favoring simple Haskell, at least at the interface level that other developers need to use to interact with a system.
So yeah, basically: Web Request -> Handler -> Do some DB queries -> Fire off some async work.
We also have risk analysis, cron jobs, batch processing systems that use the same DB and so forth.
We're starting to feel a little more pain around maybe not having enough abstraction though. Right now pretty much any developer can write SQL queries against any tables in the system, so it makes it harder for other teams to evolve the schema sometimes.
For SQL, we use a library called esqueleto, which lets us write SQL in a typesafe way, and we can export fragments of SQL for other developers to join across tables in a way that's reusable:
select $ from $ \(p1 `InnerJoin` f `InnerJoin` p2) -> do on (p2 ^. PersonId ==. f ^. FollowFollowed) on (p1 ^. PersonId ==. f ^. FollowFollower) return (p1, f, p2)
which generates this SQL:
SELECT P1., Follow., P2.* FROM Person AS P1 INNER JOIN Follow ON P1.id = Follow.follower INNER JOIN Person AS P2 ON P2.id = Follow.followed
^ It's totally possible to make subqueries, join predicates, etc. reusable with esqueleto so that other teams get at data in a blessed way, but the struggle is mostly just that the other developers don't always know where to look for the utility so they end up reinventing it.
In the end, I guess I'd assert that discoverability is the trickier component for developers currently.