A FactoryFactoryFactory in Production (2017)
stevenheidel.medium.com
stevenheidel.medium.com
LinkedIn replaced its uses of Spring with a thing called Offspring. Offspring explicitly disavowed being a dependency injection framework, but it did a similar job for us. I rather liked it. Notably, you just wrote Java with it. Invariably, in Offspring, you'd have to write a FooFactory to construct your Foo object to inject it into some (other class. By convention, all of the factories ended in Factory.
Well, I had a use case for a runtime class that needed to make a per-request factory to make little objects. So to make my Bars, I needed a BarFactory; and to construct the BarFactory, I needed an Offspring factory, thus BarFactoryFactory. There it was. I felt a little weird after that.
I suspect the EventFactoryFactoryFactory code here was such an Offspring factory being used for dependency injection, but I can't explain why it produced a FactoryFactory.
Seriously, I think anytime I have met currying (admittedly only in JS projects) the code could be rewritten to be better without it.
I think dependency injection itself is reasonable, but I increasingly wonder why you need a framework for it at all.
Yeah, for certain extremely complex scenarios, such as dynamically loaded plugins which are only known at runtime, you'd need some "host" code which manages the plugins - though even in that case, it will probably be helpful if that management code is part of your application's codebase, so you can easily debug it.
However, that's already the big exception. I'd claim that in the vast, vast majority of projects, DI is only used during development - and at runtime, there is exactly one way how the object graph is supposed to be assembled. So if that's the case, why not just make a big "application" class in your codebase and instantiate the objects/assemble the graph yourself? What exactly do you need a framework for?
Another exception are callback situations, most prominently web endpoints. A framework can be a big help here to get the headers right, manage auth, caching, CORS etc through filter chains, avoid a good part of typecasting, redundant declarations, etc.
But even there, Java now introduced lambdas and records which makes working with callbacks and "dumb structs" far easier. So the future of "web apps" here might as well be some library which lets you register web endpoints with lambdas - instead some overarching framework which expects that you structure your whole codebase around the fact that your app will serve HTTP on port 8080.
One big problem with a giant "Application" class is that it means all of the dependencies are laid out there, and their dependencies, and their dependencies, all named and instantiated. But some dependencies are in libraries, and basic detail encapsulation means ... a factory.
Offspring wasn't much of a framework, more of a set of conventions and utilities for building that stuff in plain Java code (with key annotations). In particular, I think the Offspring setup wasn't opinionated about the framework of the rest of the application, although other parts of LinkedIn were (and presumably still are).
Indeed they are - and I strongly believe that is a good thing. Because at which other place are all your dependencies laid out and instantiated? In your deployed application code at runtime.
So to understand your application in its entirety (and not just individual components), knowing the whole object graph is essential. And the easiest way to know it is if it's already written down somewhere.
This doesn't have to keep you from enforcing loose coupling and encapsulation, at least for certain definitions of those:
If you want to keep the components encapsulated - i.e. ensure that FooService works with any implementation of BarService - you can still do that: Just ensure the individual components only refer to each other through interfaces and forbid direct dependencies between them. Then in the end, the application class should be the only location in the entire codebase where multiple concrete components interact in the same class (excluding tests). You can even enforce this by putting each components in a separate artefact and only allowing dependencies to an "API" artefact that is distinct from the implementation.
However, if you understand loose coupling such that no part of the application should know about the other, and the entire application assembles its component through some magical algorithm, I'd call into question why this is even a desirable property: You still have an object graph, you just go out of your way to hide it. And it's still very important that the object graph must be assembled in some very specific way, which is why the algorithm must be coaxed into doing the right thing with qualifyer annotations or magic priority numbers.
> functional programming paradigm
It's very common to see/use F(F(F...))) paradigms in lambda calculus homework exercises and its more modern/useful derivatives (yes, like Scala) where the real "meat" of the logic is the center of this Tootsie Pop. We need to change the shape of a thing to make it fit some function that takes the thing (where every step of the way, we're greeted with: no, not like that). Often times, the function expects a functional type, but we just have an object. Better Factory it up!
So at some point down that rabbit hole, you're like "fine, what about a FactoryFactory?" ok cool, that works (because the engineer 15 years ago wanted it to be more "general"). Just add a ".build()" and commit. Merge. Done.
(a -> b -> b) -> b -> [a] -> bAgreed. That would be compose in Scala[0]. Or F |> F |> F if using the thrush combinator[1].
> Often times, the function expects a functional type, but we just have an object. Better Factory it up!
Why?
> So at some point down that rabbit hole, you're like "fine, what about a FactoryFactory?"
This is where you jumped the shark. Doing composition by encoding it as a one-off unique type ("<prefix>FactoryFactory") appears to be a "poor man's monad transformer."
Unless, of course, the intent is to just get the change req "down the road."
0 - https://www.scala-lang.org/api/2.13.11/scala/Function1.html#...
1 - https://cinish.medium.com/scala-thrush-combinator-77c8c9d9fc...
Though it does seem like perhaps the Finder itself is possibly an unnecessary abstraction.
Frankly, the longer I work, the more comfortable I am seeing these kinds of things. They can be misused, but they are also necessary for some really good things too. The absurd names are funny, but also somewhat standardized enough to be a signal on their own.
I mean, any time you use a an ORM which can use multiple backends and inject runtime/test implementation, you have 3 or 4 levels of factories to create the actual model instance. They're just named much better.
They are a form of Hungarian notation[0], made popular in MS Windows C code:
In its original form, Hungarian notation gives
semantic information about a variable, telling
you the intended use.
In this iteration, the Hungarian notation is encoded as "canonical suffix words for a class" instead of "canonical prefix characters for a variable."Both uses are, in my not-so-humble opinion, anti-patterns to be avoided.
0 - https://en.wikipedia.org/wiki/Hungarian_notation
1 - https://learn.microsoft.com/en-us/windows/win32/learnwin32/w...
For example here: https://www.tutorialspoint.com/design_pattern/factory_patter...
I'd just have a static Shape.getOfType()
Or maybe ShapeType.toShape() for type safety instead of using strings for the type.
It's okay, I'll brace for the down votes. :)
- if/else everywhere to make ShapeA/ShapeB depending on the current toolkit
- generic Shape DTO and if/else + transforming all around the dispatcher to each toolkit
- pass in ToolkitContext instance which has a .createShape() which requires a single condition at app initialisation stage
Well... that ToolkitContext is a ShapeFactory. (Among other things) Then if you want to use a nicer system of handling this, you use DI, which is then effectively a FactoryFactory. Whether it looks obnoxious each time or you simply say AppContext.Get<Shape>() depends largely on your language though.
Seems to me that what you actually want is an algebraic data type and 2 different plot functions. Visitor patterns are a neater way to achieve that.
"Seems like a bad idea" seems like a bad way to make software engineering decisions. Define what you're losing / gaining, how it impacts readability, does it simplify future development, etc.
I'm just saying what my prior is, I'm not saying I won't change my mind, but you seem to suggesting I should require less evidence to accept the option I'm least sure about.
Which, you know, seems like a bad idea.
I like me some if statements. Debugger friendly.
If statements are useful for validation and bootstrapping decisions*, but if you embed them in your model as part of the operational structure, you’re making a rookie mistake.
*High performance software and scripts are the exception to most rules.
I'm not sure what you mean by debugger friendly. Your debugger knows what types things are.
The place I've bumped into this is the "self"/"builder" pattern of setter. So for example if you have the superclass method:
abstract public Shape setArea(Number area);
it's absolutely correct for other interfaces to override it with more precise definitions that they expect their members to obey:
@Override abstract public Quadrilateral setArea(Number area);
@Override public Square setArea(Number area) { this.area = area; return this; }
The downside here is that interfaces and classes have to override all such methods with their own implementation returning the proper type, even if it's just { super.setArea(area); return this } otherwise the superclass's looser definition will be used. For example if you don't override the area method then this doesn't work:
//compiler error: method setSides is unknown for class Shape
new Square().setArea(area).setSides(sides)
But if subclasses override all their methods, for example if Square overrides the setArea method with one returning Square, the above example works.
Obviously, again, only works if you know the instance you're being passed is a Quadrilateral or a Square, but if you don't know that you are reduced to casting based on some kind of list or handler.
I see two reasons for using a factory:
1. Reducing the scope of what a piece of code is responsible for (a.k.a. "Single-Responsibility-Principle").
Assume you are working on a graphics app that can draw 50 different shapes. The currently selected tool is stored in a variable, and there is a long switch statement that returns a new Shape depending on that variable. You wouldn't want the switch statement to dominate the rest of the code:
handleClick(x, y) {
selectedTool = getSelectedTool();
shape = switch (selectedTool) {
case "CIRCLE" -> new Circle();
// 49 more lines here...
}
drawShape(shape, x, y);
}
Not only would it get more difficult to read the code, but also the test for handleClick() would have to test for all 50 shapes. If the instantiation of the shape is separated out into its own function, handleClick() can be shorter. If the factory function is injectable, the tests for handleClick() can focus on the coordination work that the function does: it asks the factory for the shape and draws it in the right place.2. Allowing to reconfigure what kind of objects are created through Dependency Injection. For instance:
class CommentRepository {
constructor(dataSource, queryFactory) { ... }
getComments(articleId) {
commentsQuery = queryFactory.getCommentsQuery(articleId);
return dataSource.query(commentsQuery).map(toResponseType);
}
}
class PostgresQueryFactory { ... }
class RedisQueryFactory { ... }Java works. Its effectiveness is high, and it’s quite uncommon to hit rough edges since it’s so well tested. A smooth ride forbackend when compared to languages like Python or Javascript.
Javascript (on the backend) is obviously a different story.
Having moved to a Python shop, I disagree. I really would like it to be true, but it's not.
The meme about factories, however, has perplexed and kind of annoyed me. Isn’t that more a symptom of once-popular design paradigms that overstayed their welcome or perhaps never were, which can happen in any long lived ecosystem (in this case, one I’ve seen pop up here and there in other OOP platforms, such as .NET)? Maybe I’m spoiled by mostly having worked in recent Spring Boot where I rarely see these tedious and awkward patterns, and perhaps the meme is more impactful for people stuck in legacy land, but there is a bit of hubris with these blanket associations that rubs me the wrong way. I’m no Java apologist, either.
Are these FactoryFactoryFactories secretly some kind of busywork conspiracy to keep all the "Coding for the sake of coding" engineers busy doing mostly harmless nonsense rather than deciding to make their own assembly language based on JSON that gets interpreted with buggy regex?
Like, I could work with a factoryfactoryfactory. It's just random nonsense, but there's not a lot of actually content.
If someone writes their own PEG parser in Forth, and then embeds a handwritten forth interpreter, and then writes the application in their own language, I'll be completely lost.
I'd rather deal with stupid boring enterprise code that does nothing than clever hacks.
I've never understood this kind of mindset. It seems to be really limiting. I've never once worked somewhere that only used on language or which didn't use java for anything.
You could pick a niche language and only ever work with that, if you really wanted to.
java has it's place. many things have a place.
That being said, for those that have drunk deeply of the functional Kool-Aid, I guess they're going to have an aversion to anything as strongly OOP-oriented as Java is. And I certainly have my own choices of tech I'd rather avoid wherever I end up going in my career, like JavaScript outside the browser. So in broad strokes I understand the "I feel disgusted coding in X" mindset.
> What does this even mean? Why does the context need to be found, provided, and factory-ed? The implementation of the class itself is 1 line of actual logic surrounded by 22 lines of boilerplate.
How are "Context", "Finder", "Provider", and "Factory" Java specific problems? Whoever decided to use that convention certainly wasn't required to do so by Java.
The purpose of factory is to make groups of related objects that are from parallel, but distinct, inheritance hierarchies.
For instance, to do AES encryption, you might need an AESKey (a kind of Key) and an AESCryptContext (a kind of CryptoContext).
Your code doesn't want to know about the existence of these classes at all, let alone their relationship.
You need some interface where you pass, say, the character string "AES" (or some enumeration, or Lisp symbol or whatever) and you get some object that makes keys and contexts of that kind.
But there was a period when Java paid the bills, so I got proficient at it. Jumped ship as soon as I was able to.
I still don't know if there's any other language with that amount of "ceremonial syntax" out there.
As annoying as Java is, please don't mistake pattern over-engineering with the language. One could choose to do the same things in any OO language, like C# or C++. And as someone pointed out here, there are similar obscenities in the FP world, they are just obscured under higher-kinded types and algebras.
... although you alluded to one problem that Java has. Its frameworks were developed during the height of design patterns and many (like Spring) are bloated or overly complex or not worth the trouble (and actually, I think design patterns, when used very carefully and in truly useful situations, can be fantastic).
Yeah, actually, I like Java (it gives application devs just the right amount of control) and think to save it, needs to purge itself of Spring.
But in practice "FactoryFactoryFactory" (*) is commonplace across codebases, frameworks, libraries, etc.
Java in the real world doesn't happen in vacuum.
(*) I know it's an extreme example, just kind of trying to make a point succinctly
Using a modern Java framework would have been such a superior approach. And it's even better now with records, pattern matching, virtual threads, and string templates (in preview).
I couldn't tell you off the top of my head exactly why any Java code here would need a Provider here vs a Factory there vs a FactoryFactory in some other place, but... without further information on what these classes were actually doing, I'm gonna withhold judgement. Once you've got words like that three deep on the end there's probably room for some refactoring, sure... but is it the most pressing thing that causes headaches when modifying the code? I doubt it.
More DI and more Factory, I kid you not. If I’ve learned anything from FP, there’s nothing you can’t solve by passing contained references to things that make more things. If it ever becomes cumbersome it’s also a great forcing function to reconsider everything you think you know and whether you want to keep knowing.
A FactoryFactoryFactory in Production - https://news.ycombinator.com/item?id=15952502 - Dec 2017 (46 comments)
Related ongoing thread:
Why I Hate Frameworks (2005) - https://news.ycombinator.com/item?id=36637655 - July 2023 (206 comments)
I wrote a Builder for a data class I needed to add stuff in a loop.
Reviewer suggested I use `copy()` instead. Once I used that I was happy, I didn't need that Builder after all. I removed bunch of useless lines of code, made things simpler. Loved it!
But, there was one situation where I had to change the name of a component/constant (can't remember what it was) that was used throughout a very large microservice (like 24 different places or something). This component was autowired using Spring. If I had to rename it by hand, it would have required doing a search and replace in all those places, but instead I had to change the name in one file. It saved me like an hour of time.
Guess what I'm saying was it saved me like in hour in this one particular situation, but cost me days worth of time in learning it's overly complex code and debugging it. Yeah, I feel as though it is a solution in search of a problem and completely not worth it (and overall, I like Java. Yeah, to save Java, Spring must find a it's way to the ash heap:).
Was thinking awhile ago they need to re-create spring boot but without spring (if this is possible)
Here is some life advice. Start passing your dependencies through the constructor instead of using a "new X()" call in the constructor. That is what dependency injection is all about.
The most common use case for me is test doubles (mocks). People who are serious about tests usually use some kind of Dependency Injection.
I think this is because of the more industrial and less academic influence on the language and frameworks
Whether it's actually worth using is another question, but at least it's a point of reference.
Anybody know what I’m talking about?