"Functional Core / Imperative Shell" is also why I love a combination postgresql/nodejs stack. The transactional sql or plpgsql surrounding the state makes dangerous operations much safer and clean. Then as you get farther from risky state changes, you get the productive flexibility of less strict javascript. You also get the option of putting complex operations in plpgsql functions when you want them executed atomic…
A Theory of Software Architecture
191–200 of 246 posts
Re: A Theory of Software Architecture
#192Earlier quoted context omitted.
> Use mostly functions, try to make most of them pure. This reminds me of: > Eat food, not too much, mostly plants > > -- Michael Pollan My new mantra: Write software, not too much, mostly functions.
"Not too much" is interesting. If I understand you correctly, you can write "too much software". I can think of at least three ways - bad architecture, too little abstraction forcing repetition, and just bad writing. Did you have something else in mind here?
Re: A Theory of Software Architecture
#193Earlier quoted context omitted.
What is it that makes you think you couldn't implement ms paint in this architecture? I've seen all sorts of thing with this sort of architecture, ranging from dungeon crawlers to distributed virtual machine orchestrators, to file system browsers, etc.
E.g. I don't see how the text tool would work. Where's the transient state stored while the text is being entered, but not yet finalized? Similarly, how would drawing a line work? That is, drawing a long squiggle, that is continuously updated while drawing, but will still be undone/redone with a single Undo/Redo operation. I simply don't see how this fits the functional core + imperative shell pattern.
From a explanatory pov: I suspect you're thinking that the mutable text state should be "core" but it's actually not, it's part of the "shell". Think about a RESTful frontend providing UI to a (mutable) database holding the state, the database conceptually is the "core" of the program, but in parlance of the architectures discussed here it would be in the "shell".
It is a bit confusing, unfortunately, and it's one of the things I dislike about this classification scheme.
Re: A Theory of Software Architecture
#194Move most of your code to pure functions, that is the number one mantra to solve most scaling problems in software development. Even in an OOP paradigm, it makes sense to make almost all objects as purely functional, with limited use of internal state. The second mantra is to think a thousand times before you name something. I actually keep a list of names (..Manager, ..aggregator,etc.) gleaned from various sources,…
This reminds me of: https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...
If one's position is that striving for a distributed model in memory is the wrong approach because of the inherent complexity associated with distributed systems that's fine but I rarely hear this argument. Typically, OOP/OOD is criticized based off of patterns that aren't proper.
Re: A Theory of Software Architecture
#195Earlier quoted context omitted.
Agree on the functional bit. Naming things: I try to not think about it for more than 10 seconds, and go with the best I've got by then. I find myself renaming things sometimes, and I'm eager to do this when a better name comes to me.
Or hopefully your code reviewer suggests names that are better if your choice doesn't make sense
Re: A Theory of Software Architecture
#196Move most of your code to pure functions, that is the number one mantra to solve most scaling problems in software development. Even in an OOP paradigm, it makes sense to make almost all objects as purely functional, with limited use of internal state. The second mantra is to think a thousand times before you name something. I actually keep a list of names (..Manager, ..aggregator,etc.) gleaned from various sources,…
https://www.infoq.com/presentations/Making-Roles-Explicit-Ud...
Re: A Theory of Software Architecture
#197Earlier quoted context omitted.
> I also dispute that it’s hard to unit test the first function—-you would simply mock the api response and then you have a great test. How many times have you seen a test pass or fail when it shouldn’t have because the mocks didn’t match the actual code? It’s a useful technique but it has drawbacks which are not easily prevented.
You would still need to use mocking to test the imperative shell
Re: A Theory of Software Architecture
#198It’s possible to build a DDD Repository in Django or Rails, but really a lot of work. I think the frameworks like sqlalchemy and NHibernate seem to do things a bit better by tracking “dirty” models, hiding the “ORM-ness”, and letting the higher levels control when to flush/write to the DB. But the more you abstract this stuff, the more you lose the auto generated sugar that makes frameworks like Django so productive.
Re: A Theory of Software Architecture
#199I've found these organizing principles to be quite useful.
1. identify the key that your application is processing (in this case, it's `word`)
2. make it so that as many functions as possible take this "key" as their only argument.
3. whenever you are fetching data about a model, it is either directly or transitively in terms of this key.
4. push model-fetching (technically the I part of I/O) as far to the leaves of your program-tree as possible, hiding them behind "model client" classes, that are passed in to your job at construction.
5. Perform all upserts idempotently, atomically, and in the bottom-right corner of the execution tree. This makes it easy to reason about mutations, and also easy to omit when you're wanting to do "dry runs" or read-only local runs.
6. (4) means you will occasionally fetch the exact the same model more than once in the scope of one execution. This is OK. You can optimize this later with execution-tree-scoped caching.
With this approach, the user-directed I/O (imperative shell) is at the root of your tree, and very narrowly-defined to be the key of computation. You can build a run-loop on top of this, or a CLI tool, or an rpc service. It's narrowness is kind to all kinds of interfaces.
The functional core is everything that isn't the leaves of the execution tree. These functions essentially compose model client calls, and only take the keys as inputs.
The leaves are a collection of reusable one-to-five-liner RPC requests / data base queries. If you like, you can wrap these in a class that caches the queries, as described in (5).
The tests for leaves mock nothing if they target a DB. They mock the rpc service if they target an RPC service.
The tests for the functional core functions mock the model-clients (leave functions).
With this structure, as long as your tests are brittle enough to break when an RPC contract or data model changes, you do not need integration tests.
I do this in Python, but it would work just as well in any language with structs/interfaces. I've observed the same benefits proclaimed in the article, although my approach is a bit different.
If people are interested, I could write more on this, with examples. Let me know!
Re: A Theory of Software Architecture
#200Earlier quoted context omitted.
"Not too much" is interesting. If I understand you correctly, you can write "too much software". I can think of at least three ways - bad architecture, too little abstraction forcing repetition, and just bad writing. Did you have something else in mind here?
I think more likely is to actually have too much abstraction.
The problem is that, as the project continues over time, the sweet spot moves...