Well, I have a very close setup to yours, the only difference is the βinfrastructure layerβ you have and I donβt. In my case, the plugs stay on the application layer (because they have web specific logic, like session control and etc), and the service and the repo go to the domain layer, which does not have any kind of web logic, just business logic.
Iβm happy with my structure for now, and itβs a pretty clear separation of concerns. My next step, I guess, is to separate the domain layer in more pieces, by resources and features and not by a specific layer itself. Something I learned with CBRA (Component Based Rails Applications) your application is a big box, and big boxes tend to become a mess if you donβt separate into mor boxes, and label them correctly.
Sounds like you are talking about bounded contexts. Identifying and maintaining the correct bounded contexts within a monolith is supposed to give some of the benefits of microservices without taking on the inherent overhead (though in the end more discipline is required). The interesting thing is that applying DRY across context boundaries can result in a βmess of couplingβ.
But Iβm wondering where to put both email delivery or file processing / uploading to s3? ACME bank has messaging as a separate module, but wojtekmach mentioned in another post that that repo is to demonstrate whatβs possible more than what you necessarily should do. So just wanted to see the general consensus.
File processing / uploading - Depends on checking user permissions (biz logic) and saving newly created s3 URL to the db (biz logic), but the details of where to upload the file and how to process/compress the file are not super tightly tied to biz logic, right? I donβt know. But Iβm kind of leaning to keeping file processing in the biz logic part of the app.
Messaging/transactional emails - Biz logic says which emails to send to whom and whenβ¦ But it doesnβt necessarily need to know whether a message was successfully sent. It could theoretically just tell the messaging app to send a message, then forget about it. So Iβm leaning toward that being a separate app.
{:ok, shop} = MyApp.Core.Account.ShopService.get(1)
{:ok, shop} = MyApp.Core.Account.ShopService.update(shop, params)
defmodule MyApp.Core.Account.ShopService do
alias MyApp.Core.Account.{ShopAuthorizer,ShopRepository,Shop}
def update(%Shop{} = shop, params) when is_map(params) do
with :ok <- ShopAuthorizer.authorize(:update, shop),
do: ShopRepository.update(shop, params)
end
end
I have mail delivery in business logic - it is single point of sending emails in my umbrella application.
If I will change UI or add additional I will still have this functional.
Interesting! I hope you donβt mind, but I have a bunch of questions =)
It seems like your app must handle p2p payments and/or payment to use the app. So you handle the payment processing in the core app? Like, use stripe or something within the core app, or did you make a separate app to handle that?
I would imagine webhooks come in via your payment processor from time to time? Do you basically forward those directly to your core app?
When a request comes in via your api, where do you put the context plug for graphql? Like, the graphql app needs the context, which is given by the user accountβs tokens grabbed from the core app, but is available via the connection in the web app? How did you put that bit together?
How did you end up handling file uploads with graphql? Are you posting to a separate non-graphql route for uploads or setting it in context?
Do your graphql resolvers ever call changesets directly or do they only hit up your services?
Are your graphql resolvers responsible for checking authorizations, or do your services ask for the authorization first, and the graphql resolver just calls the service?
Ask away! Small sidenote though, Iβm still building it
It seems like your app must handle p2p payments and/or payment to use the app. So you handle the payment processing in the core app? Like, use stripe or something within the core app, or did you make a separate app to handle that?
Not really sure about this one yet, I would like to put the general (CRUD) payment logic in the core app but Iβm still thinking about this one (and fulfilment as well).
I would imagine webhooks come in via your payment processor from time to time? Do you basically forward those directly to your core app?
I think a small router will handle the forwarding / parsing of the webhooks to the core app.
When a request comes in via your api, where do you put the context plug for graphql? Like, the graphql app needs the context, which is given by the user accountβs tokens grabbed from the core app, but is available via the connection in the web app? How did you put that bit together?
The api is an GraphQL, Plug and Cowboy app. Any plug that is needed is placed with the app itself. (same for web) Both use the core app to check the credentials and retrieve the user.
How did you end up handling file uploads with graphql? Are you posting to a separate non-graphql route for uploads or setting it in context?
No sure yet, Iβve tested a couple of setups but havenβt found a good one yet.
Do your graphql resolvers ever call changesets directly or do they only hit up your services?
Only the services
Are your graphql resolvers responsible for checking authorizations, or do your services ask for the authorization first, and the graphql resolver just calls the service?
Guardian is used in both api and web to handle the authentication (token, session, etc). Comeonin is used in the core to handle the user related functions like creating the password hash and checking them.
Thanks! This is awesome to hear how youβre doing it with graphql.
So instead of the web app forwarding all requests to /api to the graphql app schema, you have 2 ports open, one for web, one for the graphql app?
I was thinking about 1 phoenix app for web concerns. But it seems thereβs actually 2 concerns: displaying presentation layerβ¦ and routing with plugs/processing: webhooks, api, oauth, static content/spa routes.
Sounds like youβll have 1 graphql app for the api, 1 web app for static content, 1 to be built for webhooks. And you may/may not build one for oauth. So youβll have 3+ ports open I guess?
Out of curiousity, do your graphql resolvers check permissions, or do your services?
The web app is an Phoenix app that will handle the backend and the storefront. I might split it up into 2 separate apps but Iβll figure that out later.
The api app will handle all the api traffic and like web will use the core app for the persistence, lookup and other domain logic.
Webhooks will be built later but most likely will be a separate app with Plug and Cowboy.
Regarding ports, Iβm using NGiNX to reverse proxy multiple domains to the apps.
Seems the organizing principle is to put βlikeβ things into the same place rather than βputting everything to deal with subject area Xβ into the same place. One of the starting points described by the article that I linked to earlier - Bring clarity to your monolith with Bounded Contexts - is to βInvert folder structures into a flat domain-oriented groupingβ.
The organizing principle of βputting like things into the same placeβ that ultimately is responsible for βa Rails app always looking like a Rails appβ (and not communicating the actual intent behind the web app) is what inspired Architecture: The Lost Years (Ruby Midwest 2011 Keynote). That type of organization is rarely helpful in revealing the emerging (context) boundaries as the application matures (Evans, DDD p.48: ββ¦crucial discoveries always emerge during the design/implementation effortβ).
I like this approach a lot and I have similar situation with separation between Core (finishing), API (in parity with core) and Web (still to be done). It really helps the design phase not just implementation or maintenance.
@hlx itβs been some time since your response. how are you liking this design choice? @peerreynders do you have any code on github we could take a closer look at? (first impression is: looks amazing)