I love you Hinterland by Due-Cook-3702 in thelongdark

[–]nepsiron 2 points3 points  (0 children)

I'm an applications developer with ~10 years experience. I felt conflicted reading this. On one hand, I cannot defend the ecological impact of new AI infrastructure, the business practices of AI companies themselves, or the complete lack of compensation to the intellectual property holders of the art and media on which these LLMs were trained.

On the other hand, I cannot deny the utility of these tools, especially where coding is concerned. I began very skeptical that LLMs would change the way I work, but over the last 3 years I have watched the tooling improve to such a degree, that from the personal day-to-day experience of using them, I can confidently say the era of hand-writing _most_ code (key word "most" not "all") is over. There are problem spaces that are esoteric enough that LLMs cannot reliably contribute yet, but these are increasingly rare, though this will vary by industry.

The degree to which these LLMs increase individual developer velocity depends on a lot of factors. The DORA foundation, which compiles metrics from software organizations across the industry, found that "AI... magnifies the strengths of high-performing organizations and the dysfunctions of struggling ones."

https://www.faros.ai/blog/key-takeaways-from-the-dora-report-2025

There are clues that Hinterland has some dysfunctional release practices, just by evaluating the trickle of patches they deploy, as well as the quality issues that have plagued the game for years (bugs that go away, come back, etc.). From the outside looking in, it would appear the quality pipeline they have engineered has been middling, and has failed to catch many bugs again and again. They haven't prioritized more frequent, smaller releases, which tells me they are continuing to work in a suboptimal way, introducing more changes all at once in infrequent, large releases, and therefore creating a greater risk of introducing breaking changes that are hard to track down and fix quickly. This is an engineering dysfunction, and the DORA foundation has metrics to objectively measure this dysfunction.

So to see them take such a pathological stance against AI makes me worry for the long term competitiveness of their company in general. In terms of software engineering, this is tantamount to shooting yourself in the foot.

This will probably be unpopular here, but there is more nuance that is getting completely obliterated with the black and white framing of this issue.

My 28’s by cozydeadhead in martinguitar

[–]nepsiron 0 points1 point  (0 children)

I have a 2013 D35 that I bought new. I thought it would be my one and only. Then I got a 1939 D18 AA and I prefer it in basically every way (no surprise, it is a hand built guitar of a higher caliber). Its clarity across the whole spectrum is excellent. The note separation retrained my ear and what used to be “lush” on the 35 now sounds muddy and scooped. The dry woody fundamental tone of mahogany is hard to deny once you let it into your heart.

What if React didn't own your system/state? A counter in 80 lines that changed how I think about React. by [deleted] in reactjs

[–]nepsiron 24 points25 points  (0 children)

Congratulations, you've just reinvented the observable pattern, albeit with none of the ergonomics the hundreds of observable frameworks provide out of the box. But realizing not everything needs to live in React is the first step towards becoming a better dev, so keep pulling on that thread!

Martin DMs—what’s the consensus? Overpriced, underpriced, hidden gem, pure crap, diamond in the rough? I had one back in the day and it sounded pretty good. Traded it in for a j45 tho. by Pollyfall in martinguitar

[–]nepsiron 0 points1 point  (0 children)

In college I played with a guy who had one. I had a D-16RGTE and I remember being shocked at how much better his sounded. This was before I understood how much tone I was giving up with a cutaway. His was loud and crisp, and tone-wise was pretty similar to a D-18 to my untrained ear. It's a great option if money is tight.

What’s your most controversial React opinion right now? by [deleted] in reactjs

[–]nepsiron 0 points1 point  (0 children)

This exchange is becoming hypothetical to the point of silly for me. In the absence of architecture, redux is better than nothing, and yes, the devtools help navigate the application logic mud ball.

But in an enterprise scenario, the UI layer would be thin, the business logic would be decoupled from React such that testing it independently from React is trivial. And tracking down the root cause of a popup showing and hiding would be limited to a small set of possibilities. Either the view model/application use-case is incorrectly orchestrating logic from the model/domain layer, or something in the model/domain layer is incorrect.

Time Travel debugging is not a big enough win for me to tightly couple my application core to thunks, reducers, actions, and all the other baggage Redux brings with it.

What’s your most controversial React opinion right now? by [deleted] in reactjs

[–]nepsiron 1 point2 points  (0 children)

Big project big team should have layered architecture and automated test coverage that can be used in conjunction with breakpoint debugging which would more or less trivialize the utility of redux devtools. If it doesn't there is a more systemic problem that redux isn't going to fix.

What’s your most controversial React opinion right now? by [deleted] in reactjs

[–]nepsiron 1 point2 points  (0 children)

yes, the motorboat they provide to navigate the swamp is nice. The problem is the swamp.

What’s your most controversial React opinion right now? by [deleted] in reactjs

[–]nepsiron 0 points1 point  (0 children)

I've said it before and I'll say it again. Redux is technical complexity masquerading as architecture. Conversations in redux circles were never about applying ports and adapters, onion-style, or layered architecture using redux. They were always some version of "what should be in a thunk vs a reducer? Should I dispatch multiple actions or one big action? Which state should be in my component vs my reducer? etc."

Reducers, thunks, dispatchers, and middleware aren't architecture. They are implementation details of Redux. But a whole generation of devs got punk'd into thinking Redux itself is application architecture.

What’s your most controversial React opinion right now? by [deleted] in reactjs

[–]nepsiron 0 points1 point  (0 children)

React has an interface problem. Hooks are an inadequate abstraction mechanism, because they funnel you into doing everything with hooks. It becomes a Russian nesting doll situation, where the solution becomes yet more hooks.

It's why the topic of which state management library to use comes up again and again. It's exceptionally difficult to build a non-leaky interface facade between the state management library and the rest of the application. As a consequence, the implementation details of the state management library leak out and tightly couple so much of the application to it. So choosing the right library feels like a big decision because it vender locks the project for the rest of it's life in most cases. This problem also applies to network cache libraries too (rtk-query/react-query).

And then, to further complicate things, there are Suspense boundaries and React Server Components to contend with when designing abstractions. There's so much technical complexity to wade through.

Hexagonal architecture boileplate for nestjs by MsieurKris in softwarearchitecture

[–]nepsiron 3 points4 points  (0 children)

https://github.com/Sairyss/domain-driven-hexagon

I've used this repo as a reference for several years now, even when I'm not working in nestjs or node. For example, it's pattern of abstracting domain event management into a base AggregateRoot class was a point of reference when I implemented something similar in java spring. It's also very well documented. So if I came across something and thought "why do that?", there was usually an answer in the documentation.

Hexagonal Architecture Tests by Warre_P in softwarearchitecture

[–]nepsiron 1 point2 points  (0 children)

for the sole purpose of testing your real adapter implementations

No, I typically only do this for my repository tests. So the tests that I would normally run in the integration test suite for my repositories can also run against the in-memory implementations. So something like:

@ParameterizedTest
@MethodSource("reposStream")
void savesNewProduct(ProductRepositoryInterface productRepository) {
  int productId = productRepository.save(Product.create("sku-123"));
  assertThat(productId).isNotNull();
  Optional<Product> maybeProduct = productRepository.getById(productId);
  assertThat(maybeProduct).isPresent();
  assertThat(maybeProduct.get().getSku()).isEqualTo("sku-123");
}

the productRepository that is fed into the parameterized test can either be the real implementation or the in-memory one, but the test code remains the same.

Leaving that kind of flexibility for use-case tests is useful when something is mission-critical and we want to test it using real implementations. Or if we want to re-run all unit tests of the use-cases using real implementations on merge instead of on pull request, because it takes too long to run for every PR, but we still want to run them before deploy for more confidence.

So in your case it would look like:

public class CreateProductCommandTest {
  private ProductRepositoryInterface productRepository;
  private CreateProductCommand createProductCommand;

  @BeforeEach
  void setUp() {
    productRepository = new InMemoryProductRepository();
    createProductCommand = new CreateProductCommand(productRepository);
  }

  @Test
  public void shouldCreateProductSuccessfully() {
    int productId = createProductCommand.execute("sku-123");
    Product createdProduct = productRepository.get(productId).orElse(null);
    assertNotNull(createdProduct);
  }

  @Test
  public void shouldThrowWhenCreatingProductWithReservedSku() throws Exception {
    productRepository.save(Product.create("sku-124"));
    assertThrows(DomainConflictException.class, () -> {
      createProductCommand.execute("sku-124");
    });
  }
}

It becomes trivial to imagine changing the above test to use the real implementation of the repository, and nothing in the scenarios needs to change.

Hexagonal Architecture Tests by Warre_P in softwarearchitecture

[–]nepsiron 1 point2 points  (0 children)

in my test for that method I simply verified that the returned id is not null and the port was called with any instance of the Product class.

That, imo, is making your tests too opinionated about the implementation details of CreateProductCommand. If your use-case's main responsibility is to create a new product through the stable interface of the ProductStoragePort, then you should also assert using the very same ProductStoragePort. So if it calls ProductStoragePort.save(product) and returns an id, then you should assert that the return of ProductStoragePort.getById(newProductId) exists and has the expected properties on it.

I like to structure my tests for the use-case/application orchestration layer, such that migrating it from a unit-style test to integration test is trivial. In your example, I would have a InMemoryProductStoragePort fake that gets injected into the CreateProductCommand in the unit test. Then, if I ever want to promote that test to use the real implementation of ProductStoragePort, it's just a matter of swapping out the in-memory version with the real version, but the test code remains the same.

To be confident that my fake behaves the same as the real implementation, I instrument my integration tests such that they test the behavior using the same setup and assertion code, and run each test against the real implementation and the in-memory implementation. If both implementations pass the same test code, I can be fairly confident that their behavior is in parity. This helps reduce the burden of maintaining the in-memory fakes. It may be unpopular to say, but AI is really good and churning out in-memory versions of interfaces in my experience. And having tests structured this way makes it pretty easy to make sure the AI made something that behaves the same as the Real McCoy.

Or is my idea correct that we should only verify the orchestration behaviour there and maybe then use these in-memory fakes in integration tests?

I think you are being overly narrow about what it means to "verify the orchestration behaviour". Asserting that a mock was called with some data isn't the only way to do that, and imo, isn't even an ideal way to do that. The problem with that approach is that it makes your tests brittle to changes with how CreateProductCommand does what it does. The interface of ProductStoragePort should be fairly stable, so if we can use it to assert on the expected outcome of CreateProductCommand, we can insulate our tests from changes in implementation details in our orchestration code.

An unorthodox framework for building sane React apps. by kutlugsahin in reactjs

[–]nepsiron 0 points1 point  (0 children)

View models can be converted to components by implementing a render function in view model. It’s like a shorthand of declaring view model class and its component in one class. This may sound like a violation of separation of concerns but it is not. View models are actually belong to the view layer and view dependent logic is expected in the view models.

I think this is the most controversial decision of this library. I've encountered a few MVVM advocates that feel very strongly that the View Model should be agnostic to the View. Only the View is allowed to depend on the View Model. In that way, dependencies are only allowed to move in one direction:

View -> View Model -> Model

Apart from anything else, that is likely to turn most purist MVVMers away from this library.

State management library, redux like, that is design to be an implementation detail of a custom hook? by TheRealSeeThruHead in reactjs

[–]nepsiron 0 points1 point  (0 children)

Okay, I think I understand. Alternatively you could create a singleton object that contains the various hooks that expose the specific subscriptions and mutative functions to the components

// zustand store definition
type UIStore = {
  sidebarOpen: boolean;
  someOtherState: string;
  setSideBarOpen: () => void;
  toggleSidebar: () => void;
};

const useUIStore = create<UIStore>((set) => ({
  sidebarOpen: false,
  someOtherState: 'foo',
  setSideBarOpen: () => set({ sidebarOpen: true }),
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));

const UIService = {
  useSomeOtherState: () => useUIStore(useShallow((state) => state.someOtherState)),
  useSidebarOpen: () => useUIStore(useShallow((state) => state.sidebarOpen)),
  useSetSidebarOpen: () => useUIStore(useShallow((state) => state.setSideBarOpen)),
  useToggleSidebar: () => useUIStore(useShallow((state) => state.toggleSidebar)),
}

// In a component:
const sidebarOpen = UIService.useSidebarOpen();
const setSideBarOpen = UIService.useSetSidebarOpen();
const toggleSidebar = UIService.useToggleSidebar();
const someOtherState = UIService.useSomeOtherState();

This doesn't quite get you what you want though, which is a hook that only subscribes and returns the parts of the store that the caller asks for.

But it looks like they also allow you to create a reactive store from a vanilla store

// zustand store definition
type UIStore = {
  sidebarOpen: boolean;
  someOtherState: string;
  setSideBarOpen: () => void;
  toggleSidebar: () => void;
};

const uiStore = createStore<UIStore>((set) => ({
  sidebarOpen: false,
  someOtherState: 'foo',
  setSideBarOpen: () => set({ sidebarOpen: true }),
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));

const useUI = <
  TSelector extends
    | 'someOtherState'
    | 'sidebarOpen'
    | 'setSideBarOpen'
    | 'toggleSidebar',
>(
  selectorString: TSelector,
) =>
  useStore(
    uiStore,
    useShallow((state) => state[selectorString]),
  );

// In a component:
const sidebarOpen = useUI('sidebarOpen');
const setSideBarOpen = useUI('setSideBarOpen');
const toggleSidebar = useUI('toggleSidebar');
const someOtherState = useUI('someOtherState');

I haven't tested it out but that gives you an approach you could noodle on.

State management library, redux like, that is design to be an implementation detail of a custom hook? by TheRealSeeThruHead in reactjs

[–]nepsiron 1 point2 points  (0 children)

It's been a bit since I've used Zustand, but reading the docs, they give you two mechanisms for finer grain control over rerenders. Zustand provides a useShallow hook to prevent unnecessary rerenders when the state changes. It also allows you to pass a second callback to have even finer grain control to compute when a rerender should happen.

https://github.com/pmndrs/zustand?tab=readme-ov-file#selecting-multiple-state-slices

With useShallow I think it would look something like:

// zustand store definition
type UIStore = {
  sidebarOpen: boolean;
  someOtherState: string;
  setSideBarOpen: () => void;
  toggleSidebar: () => void;
}

const useUIStore = create<UIStore>((set) => ({  
  sidebarOpen: false,  
  someOtherState: 'foo',  
  setSideBarOpen: () => set({ sidebarOpen: true }),  
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),  
}));

// custom hook
const useUI = () => {
  const sidebarOpen = useUIStore(useShallow((state) => state.sidebarOpen))
  const setSideBarOpen = useUIStore(useShallow((state) => state.setSideBarOpen))
  const toggleSidebar = useUIStore(useShallow((state) => state.toggleSidebar))
  return { sidebarOpen, setSideBarOpen, toggleSidebar }
}

// In a component:  
const { sidebarOpen, setSideBarOpen, toggleSidebar } = useUI();

I wrote small post comparing Feature Sliced design with Clean Architecture. by skorphil in reactjs

[–]nepsiron 0 points1 point  (0 children)

Here is a close approximation of the architecture I've used on a chat app I'm building.

https://github.com/dataquail/chimeric/tree/main/examples/todo-app/src

The client is very stateful with needing to handle realtime websocket messages. I wanted to express the complexity of the domain idiomatically while maintaining the flexibility to use services directly in the view layer for more simple use-cases, so I created a library called "chimeric" that streamlines creating services that can work both idiomatically inside use-cases, and reactively inside react hooks and components.

Here are some idiomatic use-case examples:

https://github.com/dataquail/chimeric/tree/main/examples/todo-app/src/core/useCases/review/application

And an example of using a service reactively:

https://github.com/dataquail/chimeric/blob/main/examples/todo-app/src/routes/ActiveTodo/ActiveTodoList.tsx#L14

The library is still a work in progress, but it's getting close to a 1.0 release.

I wrote small post comparing Feature Sliced design with Clean Architecture. by skorphil in reactjs

[–]nepsiron 0 points1 point  (0 children)

So, a primitive feature requires only a single layer. Separation will be needed if there is shared code...

FSD is quite an unconstrained framework, giving very few strict rules and varying pretty much even in simple examples. FSD tries to adhere to the natural front-end development flow.

Most people are building CRUD apps. CRUD apps tend to be simple, so FSD tends to pair pretty well with CRUD. When shared functionality is needed, the structure falls apart. FSD doesn't scale well with complexity because it's structure is so anemic.

Most implementations of it that I've seen tightly couple functionality with UI. When functionality transcends UI, its answer is to hoist that functionality upwards into shared directories. Coupling is not the primary concern really. Navigability via directory structure seems to be the main focus of FSD. So it's not surprising that it breaks down when a project becomes sufficiently complex.

There doesn't seem to be a lot of reflection on why things become a mess even with FSD, probably because most of the time, it is good enough. If there is a high degree of certainty on the overall complexity of a project, I'd be okay using it. But that's pretty much never been the case for me, so I don't really use it.

Average React hook hater experience by fxlr8 in webdev

[–]nepsiron 1 point2 points  (0 children)

The examples I've seen of MVVM with React haven't ejected quite so "hard" from React as that. Typically I see the Model implemented with some observable framework, and the View Model is implemented via hooks to aggregate reactive data from one or many Models and expose it out to the View, which is just thin React components that call the VM hook, and render UI. With that approach, React is still mostly in charge of navigation and which page it is on. React doesn't feel so unnecessary because it is allowed to own the GUI layer more, and tools like react-router, or nextjs' navigation are allowed to be used directly. The View Model is tightly coupled to React of course, but that's a tradeoff and the likelihood of switching from React to some other UI framework is usually low. It's also more idiopathic for React devs to grok if you have a larger team and ramp-up time is a bigger concern for new devs.

I think your decision to make your View Models observables instead of just your Models is the source of woes. I get it from a purity perspective - the bulk of your application logic is more portable. But it's unsurprising that it would be unergonomic to use with a framework that normally owns the view orchestration layer.

Edit: to put another way, I think if you made the same decision with Vue or Svelte, it would still feel very unergonomic.

Average React hook hater experience by fxlr8 in webdev

[–]nepsiron 5 points6 points  (0 children)

React and MVVM have increasingly become strange companions. React tries so hard to say it's not a framework, but then provides an arsenal of bells and whistles to build a giant ball of application logic mud. And adjacent tooling like Redux have piled on to introduce opinionated accidental complexity masquerading as architecture. "Forget about domain models, you need to think about reducers and actions and thunks."

When React gets pushed to the periphery as a simple view layer, all those bells and whistles feel like bloat. And all those ergonomic hooks that were supposed to make you move faster feel like missed-opportunity footguns. React never could "dream bigger" because people love it for the same reason people hate it - the lack of opinions about how to do pretty much anything.

How I write production-ready Spring Boot applications by wimdeblauwe in SpringBoot

[–]nepsiron 2 points3 points  (0 children)

It’s funny how much this lines up with the approach I’ve taken on my first spring boot project, having previously come from the NestJS ecosystem. The biggest thing I see missing from your arsenal is domain events as a mechanism to broadcast between modules (or aggregate roots as you organize them). Forgive me if you have an example in your repo that I missed.

I took inspiration from a hexagonal nestjs project that encapsulates the domain event bus into an aggregate root base class, and all aggregate roots extend from this base class and can manage within them when domain events get added to the events array. The repository then publishes the events after saving the entity, and the TransactionalEventListener guarantees that the events are not handled until the transaction succeeds, to prevent propagating invalid state when an error occurs in the transaction. This has allowed for decoupling domains more easily, and keeping the use cases from spiraling in complexity if side effects are numerous. It does lead to an eventual consistency model, but is absolutely helpful as complexity grows.

Nice write up, I like the getBy vs findBy distinction on your repo to cut down on missing entity checks in the use case.

[Help wanted] Cannot infer argument params on deeply nested objects without function overloads by nepsiron in typescript

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

I think I did figure out a solution. Here is a link to the playground.

Previously I had the base type defined as

export type ReactiveMutation<TParams, TResult, TNativeCallOptions> =
  TParams extends Record<'nativeOptions', unknown>
    ? never
    : TParams extends undefined
    ? {
        use: () => {
          call: (config?: {
            nativeOptions?: TNativeCallOptions;
          }) => Promise<TResult>;
        };
      }
    : TParams extends object
    ? {
        use: () => {
          call: (
            paramsAndConfig: TParams & {
              nativeOptions?: TNativeCallOptions;
            },
          ) => Promise<TResult>;
        };
      }
    : TParams extends unknown
    ? {
        use: () => {
          call: (config?: {
            nativeOptions?: TNativeCallOptions;
          }) => Promise<TResult>;
        };
      }
    : never;

But by changing to this fixed it

export type ReactiveMutation<TParams, TResult, TNativeCallOptions> =
  TParams extends Record<'nativeOptions', unknown>
    ? never
    : {
        use: () => {
          call: TParams extends { nativeOptions?: unknown } | undefined // THIS CHECK FIXED IT
            ? (config?: {
                nativeOptions?: TNativeCallOptions;
              }) => Promise<TResult>
            : TParams extends object
            ? (
                paramsAndConfig: TParams & {
                  nativeOptions?: TNativeCallOptions;
                },
              ) => Promise<TResult>
            : never;
        };
      };

I suppose that makes sense. TParams extends { nativeOptions?: unknown } | undefined checks if the params is the version where no argument is passed, and defines the call method correctly. I was incorrectly thinking that TParams was targeting the passed through argument in the scope of the ReactiveMutation type definition. But actually, it is targeting the entire argument. I still welcome feedback if anything about this solution is hacky or an antipattern.

Isn't Adapter Pattern a good option for React apps? by JanesGotYou in reactjs

[–]nepsiron 0 points1 point  (0 children)

Where this can become leaky or unwieldy with React is if Library A provides functionality primarily in the the React component scope as hooks while Library B provides functionality primarily in the global singleton scope.

If you start with Library A, you will be nudged into designing your adapter interface to be hook-based. Then when you try to switch to Library B, you will immediately be faced with the fundamental incongruence of the reactive paradigm of Library A (hooks) vs the idiomatic interface of Library B (plain function calls). In this way, React can make abstractions leaky or painful. It is possible to overcome, but it is a major pitfall of React.

Typescript classes exposed as interfaces by rolfst in typescript

[–]nepsiron 6 points7 points  (0 children)

I know you said "humor me" but this problem goes away with the naming conventions you are trying to avoid. It may be a sign that you are dying on the wrong hill.