Why lsFusion and not 1C?





Previous article "Why not 1C?" came out more than a year ago and aroused quite a keen interest (just a little short of 100k views and 2k comments). However, as expected, many a reasonable question arose: "If not him, then who?" Of course, as many have understood, that article was not written just like that, but in order to release another one after it, where it would be told how the problems described in the first article can and should be solved. However, for various reasons, the release of this "response" article was delayed for a very long time. But as they say, better late than never.







, ( ) lsFusion. , : (function-level, functional), , -, (constraint) . , buzzwords, , , .







« 1?» ( ):









1 , lsFusion.







: , ..



lsFusion , ( lsFusion — ). ( ). , , — , « ». , , :







  • , (, - , ), , , - (, / , ). , , «».
  • «» ( lsFusion / ), .




Since lsFusion tries to make the most of the SQL server and not the application server to execute the calculation logic (and it does this by grouping queries as much as possible to execute them as little as possible), the operation of reading an entire object in lsFusion does not exist in principle. As a consequence, both the N + 1 problem and the over-read problem in lsFusion are extremely rare. For example the following action:





fillSum(Invoice i) {

    FOR invoice(InvoiceDetail id) = i DO

        sum(id) <- price(id) * quantity(id);

}





It will compile in one step:

fillSum(Invoice i) {

    sum(InvoiceDetail id) <- price(id) * quantity(id) WHERE invoice(id) = i

}





Which, in turn, will be executed with one query, in which only the rows / columns used will be read / written.



Tables / Views: Registers



lsFusion , «» 1, , , , , « » ( , ), . , lsFusion :







  • — , (, )
  • / — , , :
    • — GROUP LAST
    • , — SUM ( lsFusion , lsFusion , )


lsFusion , .







, . lsFusion , , . - :





LEDGER Sales GROUP Stock stock, Sku sku SUM NUMERIC quantity, NUMERIC sum;



//    Sales   :

// stock, sku, quantity, sum = ABSTRACT Stock, Sku, NUMERIC, NUMERIC (Sales); - 

  / 

// quantitySales, sumSales (stock, sku) -   ( =   +  )

// quantitySales, sumSales (stock, sku, DATETIME) -    

// quantitySales, sumSales (stock, sku, DATETIME, DATETIME) -     

//  ..





And in the next versions, such syntactic sugar will most likely appear. Another thing is that most often in complex projects, registers have a more complex structure (for example, they inherit from each other, denormalize data for composite indices, expand in different modules, and so on), so such sugar can be important only for RAD development (and more precisely, prototyping), which is no longer so relevant in the modern IT world.



Registers are supported in very special cases



As mentioned above, registers in lsFusion are not one big combine, but several different mechanisms, the key of which, perhaps, is the materialization mechanism (recording and automatic updating of calculated data into tables).







1, lsFusion , . , lsFusion :







  1. , , , ( " ").
  2. / / , .
  3. , , , “” ( ).
  4. (), .




lsFusion supports constraints and events in general, including computed non-materialized data. So, for example, to create a constraint that the remainder (which can be calculated using any number of different data / operators) must be greater than 0, it is enough to write just one line:





CONSTRAINT currentBalance(sku, stock) < 0 MESSAGE '    ';





Accordingly, the platform will itself as efficiently as possible (by incremental calculations) check that no change (for example, a change in the warehouse of the receipt or the amount of consumption) will violate this limitation.

Also, in the same way, you can create, for example, notifications about changes of any, including calculated, data:





WHEN SET(currentBalance(Sku sku, Stock stock) < 0//     0

    EMAIL SUBJECT '   ' + address(stock) + '   ' + name(sku) + 

'   0' TO responsibleEmail(group(sku));





Only constants can be used in virtual table parameters



- lsFusion . , , , , . , , , lsFusion . :





EXPORT FROM price(Sku sku), balance(date(sku), sku) WHERE name(sku) = '';





The platform will automatically push the constraining name condition (and as a consequence of the date on which the remainder will be calculated) into the subquery (and all subqueries inside this subquery), thus performing the predicate push down optimization . Moreover, unlike the same SQL, the platform is able to perform this optimization not only for groupings, but also for partitions and even for recursions. However, this is a topic for a separate article, we will not dwell on it in detail here.



Inquiries



, lsFusion SQL ( ), , . , , .









lsFusion, :







  • IDE — , , , ..
  • IDE ( ), , .


, / (IF, SHOWIF ..), (EVAL), lsFusion.









LsFusion has a very powerful query optimization engine internally, in many cases performing optimizations that even expensive commercial DBMSs (let alone PostgreSQL) cannot do. So, all the performance problems described in the article "Why not SQL" , lsFusion is able to solve independently without any additional actions on the part of the developer, who, accordingly, can concentrate on solving business problems, and not think about how to write a query correctly and / or in which temporary table to put its result.







So an example from an article about 1C in lsFusion will look like this:







Example from the article

    .,
    .

    ..  
          ..(,
                               (
                                    
                                    ..
                                     = &))  
         . = .

    . = & 
    (. < . 
        .  NULL)
      
      



currentBalance(InvoiceDetail id) = currentBalance(sku(id));



export(Invoice i) {

    EXPORT FROM sku(InvoiceDetail id), currentBalance(id) WHERE invoice(id) = i AND 

currentBalance(id) < quantity(id) OR NOT currentBalance(id);

}





Accordingly, no "Nomenclature B (SELECT Nomenclature FROM Document.Invoice.Content WHERE Reference = & Document)" in lsFusion does not need to be written.



SQL



SQL-92, ( SQL — ), lsFusion :







  • / ( SQL — )
  • ( SQL — CTE)
  • ( SQL — )


lsFusion - ( GROUP BY ).









ERP- ORM , , - , ERP- SQL- .







However, in the same 1C queries are supported only for data read operations; for writing, you still have to use ORM mechanisms, the performance of which leaves much to be desired. In lsFusion, there is no such problem, and all operations, including the creation of objects, can be performed on the database server, and with one request. For instance:





generateCards() {

    FOR iterate(i,1,10000NEW d = DiscountCard DO

            number(d) ← ‘Card:’+i;

}







Ultimately, it will be compiled into one request (or several, but their number will not depend on the amount of data) and will be executed very quickly, and all events / constraints / aggregations will also be executed / checked / recalculated by a limited number of requests (again, independent on the amount of data).

The same applies to the mechanism for changing / deleting a large amount of data / objects:





FOR sum(DiscountCard d) > 10000 DO

    vip (d) ← TRUE;

FOR sum(DiscountCard d) > 10000 DO

    DELETE d;





Will compile to:

sum(DiscountCard d) ← TRUE  WHERE sum(d) > 10000;

DELETE DiscountCard d WHERE sum(d) > 10000;





And again, it will be executed in one request.





( ) 1, . :







  1. ( MS SQL).
  2. Repeatable Read Serializable.
  3. , .
  4. .


lsFusion. , 1 ( ) lsFusion:







  • , ( , ),
  • , , « ».




lsFusion — , / .







:



Unlike 1C in lsFusion, the execution flow is the same for both the server and the client. This unity greatly simplifies the interaction with the user / client device in terms of the development process. So, the example in the article about 1C is written in the lsFusion language, and, accordingly, looks like this:





f() <- someData(); //       myForm

DIALOG myForm OBJECTS a INPUT DO // , 

 - 

     IF isSomething(a) DO //          - 

 

         DIALOG otherForm OBJECTS b = a DO { // ,  

       b

             g(b) <- someInput(b); //    b

             APPLY//    

         }





To some extent, lsFusion uses the approach of conventional forms in 1C, only it makes it much more scalable and productive. In fact, all the magic of asynchrony remains under the hood, and the developer can concentrate strictly on solving business problems, rather than thinking about where and how the code he writes should execute.

, lsFusion ( /). , lsFusion CLIENT INTERNAL, . Java, - — JavaScript. “ ” , .









- ( ) “” ( , , ). (, ), , , 1 ( , async / await, ).







lsFusion , , , , .







WYSIWYG:



, 1 , ( ), 2 :







  • - , / ().
  • .


1 . lsFusion , , , «Excel-style» , , , , , . ( lsFusion) « » — / . , .







/



In lsFusion, when setting properties, filters and other elements on a form, you can access all objects at once, even if they appear in different lists (or other views). At the same time, the platform itself monitors changes in objects (as well as changes in data) and automatically updates form data using these objects. For example, if you create the following form:





FORM balance

    OBJECTS st = Stock, sk = Sku

    PROPERTIES (st) name

    PROPERTIES name(sk), currentBalance(st, sk)

    FILTERS currentBalance(st, sk)

;





When you move the current record in the upper list (warehouses), the lower list (products that are in the selected warehouse) will be updated automatically.



Redundant levels of abstraction



The main principle when creating lsFusion was and remains the principle - the purity and completeness of all created abstractions. So:







  • lsFusion . — . , .
  • ( ) , , , ( ).
    :




, lsFusion 1. 1 lsFusion:







  • /


lsFusion ( ) . , ( , ..) , «» ( ).







  • /


, 1 - lsFusion ( , ).







  • /


lsFusion , . - « » lsFusion . , . - , , ( ). lsFusion .







  • / / (BI)


. , / lsFusion ( ). , ( , ) lsFusion :







  • , JasperReports, Java. pixel-perfect , .
  • , , , .

  • — «» , , « » ( ).
  • , ( ) JSON, XML, XLSX, DBF .


lsFusion — . , , . , , / .









lsFusion , lsFusion , / ( 1). lsFusion — , . , , , ( ). - .







:







  1. ( BI).
  2. (, )
  3. .




PS: « » ( ) « 1?» , , , , . , , .







As mentioned in the previous section, the mapping of data logic in lsFusion to a relational database is transparent and can be completely controlled by the developer. Together with materializations in general and indexes, a developer (and even an administrator) can achieve almost any performance, even on huge amounts of data. Moreover, since the platform itself monitors changes in the physical model and updates the database structure without any additional migrations, the performance optimization process can (and should) be performed on a running database when the statistics and options for using this database are known. So let's say we have a simple example:





date = DATA DATE (DocumentDetail)

barcode = DATA STRING (Sku);

sku = DATA Sku (DocumentDetail); 



barcode(DocumentDetail dd) = barcode(sku(dd));

count (STRING bc, DATE d) = GROUP SUM 1 IF date(DocumentDetail dd) > d AND barcode(dd) = bc;

FORM x

        OBJECTS bc = STRING PANEL, d = DATE PANEL

        PROPERTIES count(bc, d), VALUE (bc), VALUE(d)

;





When executing this form, a request will be generated in which it will be:

  1. JOIN with the product table, the barcode in the SKU table matches the specified one;
  2. counting the number of document lines for all dates greater than the specified one.


In this case, the SQL server will have two options: either run by the index by dates in the table of rows, or run by the index by barcodes in the table of goods, find goods, and then run by the index by Sku in the table of rows. In both cases, the performance will leave much to be desired (if there are a lot of movements of one product and a lot of products). In lsFusion, to solve this problem, it is enough to change / add the following lines:





barcode(DocumentDetail dd) = barcode(sku(d)) MATERIALIZED// ,  

   

INDEX barcode(DocumentDetail dd), date(dd);//   





After this optimization, the SQL server will be able to start using the constructed composite index and the performance will be maximum.



Closed sources and licenses



- . Microsoft, , .Net, Linux.







, , — ERP-, , : — . , .







lsFusion LGPL v3 , , ( ), . GitHub. Maven-, Maven: compile, install, package .. , , GitHub Projects. , .









. lsFusion . , ( ), ( ).







( ), , ( ).







lsFusion , tutorial, , .









lsFusion . ( ), . , ( ) .







, , , , , . :





invoice (InvoiceDetail id) = DATA Invoice;

sum = GROUP SUM sum(InvoiceDetail id) BY invoice(id) //  sum   

  Invoice (    invoce,    - Invoice)



FORM myForm

    OBJECTS myObject = MyClass

;

filtered = FILTER myForm.myObject ; //  filtered      

MyClass (    myObject  myForm)









, , - , custom-made . , , . , / «», .







lsFusion . :







  1. ( , — ) — - . , , , , / .
  2. — . - - (, ).
  3. — , ( ). , - ( «» / , ).
  4. , ( ). , this, , lsFusion , - «».
  5. — , , - ( )


, , , lsFusion ( ), lsFusion / , .







, «» lsFusion — ( ) , , . ( , ), ( ).









, , Everything as code . lsFusion.







, , lsFusion . , , ( ). lsFusion IDEA : , , , .. -, , .









, , , — 1, ERP-. , , , :







  1. .
  2. , , , .


lsFusion : - , - . - , .







, lsFusion:
  1. . “ - ”. Java . , 1, lsFusion . , , , . , . . , .







    , lsFusion — lsFusion ANTLR, IDEA Grammar-Kit (), JFlex ().





  2. UI. - Java SE (Swing, Web Start), , . , , -, - .







    - lsFusion :





    • GWT — Java (), . , , , , GWT , . GWT JavaScript, JavaScript







      , GWT , TypeScript . :







      ) - TypeScript - ;







      ) lsFusion GWT , , .







      - TypeScript, , .





    • Full Calendar, Leaflet — “” ( ).
    • Spring Security, MVC, DI — , , (, ).
  3. BI — “” lsFusion “ ”. :





    • pivot-table, subtotal — BI, ( ),
    • plotly — ,
    • tableToExcel — Excel ( , collapsible ..).


    , ( , ), open-source, - — .





  4. . lsFusion — JasperReports.







    . 1 , , , , :







    ) , , , 4 ;







    ) “”, pixel-perfect .







    lsFusion : ( renderer’, , . .), . BI ( , . .), ( ).





  5. IDE. IDE, IDEA (Eclipse ), IDEA . , , IDEA , . IDEA ( IntelliJ Platform) IDE, , lsFusion ( , lsFusion ). stub index’, chameleon element' lsFusion (, , ).
  6. . Everything as code , , , Git. , Subversion (, , ).
  7. / . EaC / Java, Maven ( lsFusion repo.lsfusion.org).







    Maven- pom.xml :







    <repositories>
            <repository>
                <id>lsfusion</id>
                <name>lsFusion Public Repository</name>
                <url>http://repo.lsfusion.org</url>
            </repository>
    </repositories>
    
          
          





    , Maven Java . , , Maven , pom.xml.







    <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-math3</artifactId>
            <version>3.2</version>
    </dependency>
    
          
          





    IDE, .





  8. . JDBC, / . Postgres ( Docker, yum ..)







    Java Spring, .











, lsFusion LGPL v3.0, , , , lsFusion . , lsFusion , , . , lsFusion , , , , / , . -? , «-» , , , — , -. , ( , , «as is», «to be»). , , , «», , :







  • — , , . «», — .
  • ,


, lsFusion (- / lsFusion, ), (, , ).







, , . , , / , , , .







. , ( - ), ( / ). ( IT) , , , ( ). MyCompany. , , , , , , .









, , - — . , . , , , :







  1. - ( ), , / . .
  2. , , , ( , , ). , , , , . , 30 3000 , - .


, , . , lsFusion , 1 ERP-.







, :

«» ( )



( , ) , / .









( ), , , - . . . .







( Google docs)



, ( , , , ).









, ( ). , .









1, , . ( ), , , , , ( ). , :







  1. , .
  2. , , , « » ( , ).


/



:







  • — , drag-drop .
  • — WYSIWYG , - ( ).
  • — > ( -> , ).
  • , — ( )


, ( -).







/



1 :







  • — , (, , Odoo lsFusion)
  • — renderer, / , , , «» js- ( , ).


( )



, :







  • ( )
  • ( / )
  • ..


1 «-» , 1 / , , .









, « 1», — - 1 lsFusion ( , ). , 1- , .










All Articles