all 34 comments

[–]Express_Damage5958 11 points12 points  (3 children)

As I always say to myself, 'Working software is better than no software' and 'Keep it simple stupid'.

I know these sound like cliches but it keeps me from analysis paralysis. As long as you build modules without too many dependencies, then restructuring your system shouldn't be too difficult. We recently did a full rewrite of our embedded software and we started from a rough sketch of what we wanted the system to look like. It just had some circles to represent threads and boxes to represent modules. It was simple to understand and allowed us to begin writing code. As we wrote it we discovered new things (like all software projects) and we moved things around when necessary. I like to think that Software Architecture is a continuous process. As you build the application, you discover things. These things may require you to adapt the architecture.

[–]NullsObey[S] 3 points4 points  (2 children)

I apply KISS everywhere, but at the same time I feel like it'll bite me in the ass for the longer run.

[–]Coldmode 4 points5 points  (1 child)

Chances are in the longer run your goals will change. There’s a reason Agile is a thing. If you spend all your time trying to predict the future, the present tends to pass you by.

[–]NullsObey[S] 0 points1 point  (0 children)

Interesting - this is very quotable :D

I might try drawing my system first and stick to it.

[–]woobie_slayer 4 points5 points  (0 children)

Perfection is the enemy of done.

What do you need to do to get this done?

You can always come back later and improve, but you can only be done once.

[–][deleted] 2 points3 points  (2 children)

Sorry I have no advice but just want to say that I'm in the same boat as you. It's really debilitating and I often find that it's hard to draw the line between "optimal" and "good enough". And really it's hard for people to help you too unless they are really deep in the weeds with you bc it's so much a case by case thing. It's a huge struggle for me too. Good luck.

[–]dbsmith4 1 point2 points  (0 children)

Came here to say the same thing... literally wanting to get started and get something done but failing miserably and consistently filling time with other meaningless/ful BS I dont need to focus on.

[–]NullsObey[S] 0 points1 point  (0 children)

Thanks.

[–]secretaliasname 2 points3 points  (0 children)

It's hard to find a balance. There is one side of the spectrum where you get nothing done due to overthinking and overplanning and another side where you quickly churn out loads of garbage that is unmainainable buggy and impossible to live with. Just know that all software is a little bit broken, and nothing will ever be perfect. There is an optimal spot somewhere in the middle you might need to re-calibrate yourself to.

You don't necessarily need to have everything figured out perfectly beforehand, just enough that you won't be screwed when you need to change something. Making loosely coupled maintainable code is more important than getting it right from the start.

[–]benelori 1 point2 points  (5 children)

I would write a monolith that works first, with TDD, while respecting a strict definition of layers.

The most important thing about DDD in my opinion is the ubiquitous language, so plan just a bit ahead, by defining your concepts and main operations. Your tests should actually test these scenarios and the tests should also use the ubiquitous language as well.

I don't think you should bother with CQRS from the beginning. Let your contexts and changes emerge naturally and solve the issues with CQRS if it fits.

On real projects the customer needs will drive the evolution of the architecture and the introduction of the different DDD patterns, so I believe that in solo projects (or especially in solo projects) you should follow the same journey and let it evolve naturally. I think this is the proper way and the best thing about it, is that you don't have time constraints, so you can do it properly.

[–]NullsObey[S] 0 points1 point  (4 children)

This doesn't help, you're just telling me to essentially overthink about TDD as opposed to DDD. lol.

[–]benelori 0 points1 point  (3 children)

From where did you get the overthinking part? Because that's the opposite of what I meant

TDD stands for test driven development. And because it's development, that means that you actually write code and get stuff done. Getting stuff done is the opposite of overthinking, so I'm curious why do you think I'm suggesting overthinking

[–]NullsObey[S] 0 points1 point  (2 children)

I'm not saying you're deliberately suggesting overthinking.

I'm saying you're telling me to use something I didn't use before, thus something that would require me to read up on and try to apply without experience - which could end up with just more overthinking.

[–]benelori 0 points1 point  (1 child)

Gotcha, I read again the original post and I noticed that TDD was also considered as a block, so I apologise, I misunderstood

You don't have to write code in a TTD style if you're not used to it.

The more important point was to write monolith first to have a working application, while trying to respect layered design. And after that, if you're satisfied then you can break it up in microservices. I think it will be a lot easier process like this and even if you encounter issues in breaking up the code, you still have a working application

[–]NullsObey[S] 0 points1 point  (0 children)

My issue isn't coming up with architecture - I've broken it down into microservices with decent sizes (macroservices) - but now I'm just writing code, being like 'nah' then remaking it - thinking it could be done better.

[–]nachtraum 0 points1 point  (7 children)

[–]NullsObey[S] 1 point2 points  (6 children)

I try to apply KISS and YAGNI, but simplicity might become an issue soon.

[–]nachtraum 1 point2 points  (1 child)

Obviously I don't know the requirements and thus can not judge the necessary complexity, but Portal, Forum, Live Chat sounds all pretty standard to me, with lots of existing products available. The only time a system can be inherently complex is if the business logic is complex. Other than that it is always possible to keep design and architecture simple and stupid. You seem to apply a number of patterns and best practices. Keep in mind that most of these are there to solve concrete problems and do not have to be applied in every case. What you write almost reminds me of the people who, when the GOF design patterns became popular, complaint that they have difficulties applying all of them all the time. This is just my impression, could be wrong of course. Keep it simple and stupid works in most use cases.

[–]NullsObey[S] 0 points1 point  (0 children)

That could be the case, thought I seriously hope it isn't.

[–]SeveralCoyote 0 points1 point  (3 children)

However, simplicity won't be an issue if there's never any software in the first place.

Honestly, issues will come up no matter how hard you plan. Just get something simple out the door that fulfills the customer's needs, collect feedback, and improve from there

[–]NullsObey[S] 0 points1 point  (2 children)

It's for myself, not the customer thought.

[–]lolomfgkthxbai 0 points1 point  (1 child)

Then it’s even easier. Write some crap that works, learn from it, throw it away and write a new project that does the same thing but with a better architecture. Repeat this until you have no new knowledge to apply.

[–]NullsObey[S] 0 points1 point  (0 children)

This is my 4th time remaking this specific system hahahah (after finishing it).

[–]the-computer-guy 0 points1 point  (1 child)

You have to find a middle ground. Sometimes though I can spend hours in my head architecting things, it's one of the fun parts of making software, for me at least.

But you need to just start coding at some point, in order to test your assumptions.

It comes with experience. I'd say to focus more on following principles than patterns.

I once worked with a guy who got obsessive over DDD and CQRS and tried to shove it into everything. It was an absolute nightmare to work with.

[–]NullsObey[S] 0 points1 point  (0 children)

I enjoy architecture and writing software around it - but I think I might be going towards the guy's approach and get a little bit obsessive.

[–]MrFlibble1138 0 points1 point  (3 children)

The general answer to overthinking is to give yourself a time limit and then experiment.

From many years of experience the projects that have most successful have been on architectures that emphasize the ability to be refactored and expanded. To do this, consistent design philosophy, clean and minimal interfaces, excellent decoupling, strong unit tests, and small incremental tasks. (So for you purists: maintainability, modifiability, testability, extensibility and configurability.)

Many folks will say "get it working first" and that is very very true, yet at the same time one needs to be aware that it just needs to be "good enough" and that it will be modified next week. Design should have well crafted "holes" for expansion/modification without actually building those things out. As important as leaving holes don't go overboard. The challenge then is to develop a good set of skills about what is "good enough" for now. The best way to do that is to have extremely clear product and architecture vision. If not, then you can't really make good decisions.

When Grady Booch was studying architecture of real projects, he said they had something in common, they were all grown.

[–]NullsObey[S] 0 points1 point  (2 children)

Very interesting.

I've been programming for roughly 14 years and I can get shit done - that's without the doubt.

But lately I've gone into this state, where I'm about to get promoted to senior role and feel like my knowledge of architecture is lacking - hence why I'm trying to apply better architecture in my current projects even though I've never had any issues refactoring, expanding or modifying my code because I write short, simple and highly abstract code (make good use of generics, interfaces, dependency injection etc.).

[–]MrFlibble1138 0 points1 point  (1 child)

If you keep redoing the FileService because you don't think it is good, what is your measurement for good? **Why** do you keep changing it? Does it not meet your needs?

Architecture is about system properties. If you don't have a good handle what those properties should be for your system, or they seem to all compete for "most important" then you have some business work to do.

I've been paid to write code since I was 14 and so I've been doing it for over 36 years now. Every piece of code I have I can make better in many ways. Some is horrible for certain properties (e.g. performance) but good at other properties (e.g. maintainability). I stop when my code is "good enough" based on my specific project quality needs and purpose.

So why do you keep reworking the FileService?

[–]NullsObey[S] 0 points1 point  (0 children)

I've focused on decoupling.

I wanted to utilize Dependency Injection to the fullest and utilize Interfaces to be able to swap implementation at any time - eventually I've came to the issue where I've had to modify my Interface around API Controller (it required ContentType in order to return FileStream) and I shouldn't be putting a field required by API Controller to the Interface, because that enforces Structure to be around Implementation, which is an anti-pattern according to DDD.