Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In my experience with enterprise software one of the things that cause most trouble is premature modularization (sibling to the famous premature optimization).

Just like I can't understand how people can come up with the right tests before the code in TDD, I can't understand how people can come up with the right microservices before they start developing the solution.



Perhaps a better way to think of "writing the tests first" in TDD approaches (or, more generally, test-first development, which is a term that has gone out of favor) is that you write test cases in that testing framework to express your intent and then from there can start to exercise them to ensure correctness. It's not a crime to change them later if you realize you have to tweak a return type. But writing them up front necessitates a greater depth of thinking than just jumping in to start hacking.

Not doing this runs the risk of testing what you did, not testing that it's right. You can get lucky, and do it right the first time. Or you can make a bunch of tests that test to make sure nothing changed, with no eye as to whether it's what anybody actually wanted in the first place.

Sitting down with somebody for whom TDD is natural is educational and makes it very hard to argue against it, particularly for library code.


The issue with most TDD or even BDD I have seen is that it is usually worthless...

You end up testing that numbers came out of a function or that some thing was called, but it doesn't actually solve the real issue which is to get your use cases correct. Instead it encourages you to break down the problem into a bunch of unrelated bits, it doesn't actually check that your approach is correct or what the customer wants, just that you wrote some bits of code which do things, whether those things are the right things...

As it is often practiced it is usually a failsafe for people who struggle to write code at all.

Acceptance tests are too happy pathy, integration tests are rarely done or deemed 'unnecessary', so the only place left to 'think about the problem' becomes actually writing/designing the code. And so tests tend to come last, because you already decided what works, and they check nonsense.

For truly difficult code, such as a mathematical algorithm which is difficult to break down, unit tests make sense, the majority of "when X is true do A, when X is false do B" of unit tests are utter garbage.


> premature modularization

I'm stealing that. I have seen soo many small projects crumble due to sheer unnecessary complexity introduced by modularization dogmas.


Well when you hire 40 engineers and 4-5 dev managers, each manager wants an application they can call their own, so they divvy up the potential product into what they feel are reasonable bits. Hence: micro services. It’s the same reason React was created: to more easily Conways-Law (used as a verb) the codebase


On the TDD argument, you should know what a return for a function f should be when given an argument b. Ideally, you have a set of arguments B to which b belongs, and a set of results C to which the return belongs. Your tests codifies mapping examples from B to C so that you can discover an f that produces the mapping. Take another step and generate random valid inputs and you can have property-based testing. Add a sufficient type system, and a lot of your properties can be covered by the type system itself, and input generators can be derived from the types automatically. So your tests can be very strong evidence, but not proof, that your function under test works as expected. There's always a risk that you modeled the input or output incorrectly, especially if you are not the intended user of the function. But that's why you need user validation prior to final deployment to production.

Likewise, with a microservice architecture, you have requirements that define a set of data C that must be available for an application that provides get/post/put/delete/events in a set B to your service over a transport protocol. You need to provide access to this data via the same transport protocol, transform the input protocol to a specified output protocol.

You also have operational concerns, like logging that takes messages in a set C and stores them. And monitoring, and authorization, etc. These are present in every request/response cycle.

So, you now split the application into request/response services across the routing boundary-> 1 route = 1 backing model. That service can call other apis as needed. And that's it. It's not hard. It's not even module-level split depolyment. It's function-level deployment in most serverless architectures that is recommended because it offers the most isolation, while combining services makes deployment easier, that's mostly a case of splitting deployment across several deployment templates that are all alike and can be managed as sets by deployment technologies like Cloudformation and Terraform [1].

You can also think of boundaries like this: services in any SOA are just like software modules in any program - they should obey open/closed and have strong cohesion [2] to belong in a singular deployment service.

Then you measure and monitor. If two services always scale together, and mutually call each other, it's likely that they are actually one module and you won't effect cohesion by deploying them as a single service to replace the two existing ones. easing ops overhead.

Not deploying and running as a monolith doesn't mean not putting the code to be run into the same scm/multiproject build as a monorepo for easy shared cross-service message schema refactoring, dependency management, and version ingredient. That comes with its own set of problems -- service projects within the repo that do not change or use new portions of the comm message message schema shouldn't redeploy with new shared artifact dependencies; basically everything should still deploy incrementally and independently, scaling it is hard (see Alphabet/Google's or Twitter's monorepo management practices, for example); but there seems to be an extra scale rank beyond Enterprise size that applies to, it's very unlikely you are in that category, and if you are you'll know it immediately.

We like to market microservice architecture as an engineering concern. But it's really about ops costs in the end. Lambdas for services that aren't constantly active tend to cost less than containers/vps/compute instances.

1: http://serverless.com//blog/serverless-architecture-code-pat...

2: http://www.cs.sjsu.edu/faculty/pearce/modules/lectures/ood/m...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: